diff --git a/.github/workflows/claude-evaluation.yml b/.github/workflows/claude-evaluation.yml index 0aad55b2c..d91282bf2 100644 --- a/.github/workflows/claude-evaluation.yml +++ b/.github/workflows/claude-evaluation.yml @@ -23,6 +23,7 @@ on: options: - "bug-fix" - "test-generation" + - "code-review" test-run: description: "Indicate this is a test run (with few entries)" required: false diff --git a/.github/workflows/copilot-evaluation.yml b/.github/workflows/copilot-evaluation.yml index 338e7f5ef..b7bcb251b 100644 --- a/.github/workflows/copilot-evaluation.yml +++ b/.github/workflows/copilot-evaluation.yml @@ -30,6 +30,7 @@ on: options: - "bug-fix" - "test-generation" + - "code-review" test-run: description: "Indicate this is a test run (with few entries)" required: false @@ -83,6 +84,7 @@ jobs: permissions: contents: read id-token: write + copilot-requests: write name: ${{ matrix.entry }} strategy: fail-fast: false @@ -122,21 +124,11 @@ jobs: - name: Install GitHub Copilot CLI run: npm install -g @github/copilot@1.0.57 - - name: Select PAT based on job index - id: select-pat - shell: pwsh - run: | - $patIndex = ${{ strategy.job-index }} % 4 - echo "pat_index=$patIndex" >> $env:GITHUB_OUTPUT - - name: Run GitHub Copilot CLI for entry ${{ matrix.entry }} timeout-minutes: 120 shell: pwsh env: - COPILOT_GITHUB_TOKEN: ${{ steps.select-pat.outputs.pat_index == '0' && - secrets.COPILOT_PAT || (steps.select-pat.outputs.pat_index == '1' && - secrets.COPILOT_PAT2 || (steps.select-pat.outputs.pat_index == '2'&& - secrets.COPILOT_PAT3 || secrets.COPILOT_PAT4)) }} + COPILOT_GITHUB_TOKEN: ${{ github.token }} run: | Write-Output "::add-mask::$env:COPILOT_GITHUB_TOKEN" @@ -153,7 +145,9 @@ jobs: if: always() with: name: evaluation-results-${{ github.run_id }}-${{ matrix.entry }} - path: ${{ env.EVALUATION_RESULTS_DIR }}/**/*.jsonl + path: | + ${{ env.EVALUATION_RESULTS_DIR }}/**/*.jsonl + ${{ env.EVALUATION_RESULTS_DIR }}/**/*.log retention-days: ${{ inputs.test-run && 1 || 30 }} summarize-results: diff --git a/.github/workflows/summarize-results.yml b/.github/workflows/summarize-results.yml index 2f5b252fe..785262354 100644 --- a/.github/workflows/summarize-results.yml +++ b/.github/workflows/summarize-results.yml @@ -108,7 +108,8 @@ jobs: --use-capi ${{ !inputs.mock && '--storage braintrust --storage kusto' || '' }} - name: Update leaderboard in a new branch - if: ${{ !inputs.mock && !inputs.skip-leaderboard }} + # WIP for code-review category + if: ${{ !inputs.mock && !inputs.skip-leaderboard && inputs.category != 'code-review' }} run: | git fetch origin main diff --git a/dataset/codereview.jsonl b/dataset/codereview.jsonl new file mode 100644 index 000000000..a769bb9a5 --- /dev/null +++ b/dataset/codereview.jsonl @@ -0,0 +1,81 @@ +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-001", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/SecureAPIManager.Codeunit.al b/src/SecureAPIManager.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SecureAPIManager.Codeunit.al\n@@ -0,0 +1,46 @@\n+codeunit 50100 \"Secure API Manager\"\n+{\n+ Access = Internal;\n+\n+ var\n+ KeyNotConfiguredErr: Label 'API key is not configured for %1.', Comment = '%1 = configuration code';\n+ RequestFailedErr: Label 'The API request failed. Check the configuration.', Comment = 'Shown when an outbound API call fails.';\n+ EndpointTok: Label 'https://api.businesscentral.dynamics.com/v2.0/data', Locked = true;\n+ BearerTok: Label 'Bearer %1', Locked = true;\n+ StorageKeyTok: Label 'ApiKey_%1', Locked = true;\n+\n+ [NonDebuggable]\n+ procedure StoreKey(ConfigCode: Code[20]; KeyValue: SecretText)\n+ begin\n+ IsolatedStorage.SetEncrypted(GetStorageKey(ConfigCode), KeyValue, DataScope::Module);\n+ end;\n+\n+ [NonDebuggable]\n+ procedure GetKey(ConfigCode: Code[20]) Result: SecretText\n+ begin\n+ if not IsolatedStorage.Contains(GetStorageKey(ConfigCode), DataScope::Module) then\n+ Error(KeyNotConfiguredErr, ConfigCode);\n+ IsolatedStorage.Get(GetStorageKey(ConfigCode), DataScope::Module, Result);\n+ end;\n+\n+ procedure CallEndpoint(ConfigCode: Code[20])\n+ var\n+ Client: HttpClient;\n+ Headers: HttpHeaders;\n+ Response: HttpResponseMessage;\n+ AuthHeader: SecretText;\n+ begin\n+ AuthHeader := SecretStrSubstNo(BearerTok, GetKey(ConfigCode));\n+ Headers := Client.DefaultRequestHeaders();\n+ Headers.Add('Authorization', AuthHeader);\n+ if not Client.Get(EndpointTok, Response) then\n+ Error(RequestFailedErr);\n+ if not Response.IsSuccessStatusCode() then\n+ Error(RequestFailedErr);\n+ end;\n+\n+ local procedure GetStorageKey(ConfigCode: Code[20]): Text[50]\n+ begin\n+ exit(CopyStr(StrSubstNo(StorageKeyTok, ConfigCode), 1, 50));\n+ end;\n+}\ndiff --git a/src/HardcodedSecretClient.Codeunit.al b/src/HardcodedSecretClient.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/HardcodedSecretClient.Codeunit.al\n@@ -0,0 +1,18 @@\n+codeunit 50301 \"Hardcoded Secret Client\"\n+{\n+ procedure CallApi()\n+ var\n+ HttpClient: HttpClient;\n+ HttpHeaders: HttpHeaders;\n+ HttpResponseMessage: HttpResponseMessage;\n+ begin\n+ HttpHeaders := HttpClient.DefaultRequestHeaders();\n+ HttpHeaders.Add('X-Api-Key', this.GetApiKey());\n+ HttpClient.Get('https://api.contoso.com/data', HttpResponseMessage);\n+ end;\n+\n+ local procedure GetApiKey(): Text\n+ begin\n+ exit('sk-1234567890abcdef');\n+ end;\n+}\n", "expected_comments": [{"file": "src/HardcodedSecretClient.Codeunit.al", "line_start": 16, "line_end": 16, "severity": "critical", "domain": "security", "body": "Hardcoded API key in source code. Retrieve secrets from encrypted isolated storage or another secure store instead."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "Clean codeunit using SecretText, NonDebuggable, IsolatedStorage.SetEncrypted, and HTTPS enforcement with no security issues", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-002", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/SecureKeyManager.Codeunit.al b/src/SecureKeyManager.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SecureKeyManager.Codeunit.al\n@@ -0,0 +1,26 @@\n+codeunit 50101 \"Secure Key Manager\"\n+{\n+ Access = Internal;\n+\n+ var\n+ KeyNotFoundErr: Label 'The requested key was not found. Configure it before use.';\n+\n+ [NonDebuggable]\n+ procedure StoreEncryptedKey(KeyName: Code[20]; KeyValue: SecretText)\n+ begin\n+ IsolatedStorage.SetEncrypted(KeyName, KeyValue, DataScope::Module);\n+ end;\n+\n+ [NonDebuggable]\n+ procedure RetrieveKey(KeyName: Code[20]) Result: SecretText\n+ begin\n+ if not IsolatedStorage.Contains(KeyName, DataScope::Module) then\n+ Error(KeyNotFoundErr);\n+ IsolatedStorage.Get(KeyName, DataScope::Module, Result);\n+ end;\n+\n+ procedure HasKey(KeyName: Code[20]): Boolean\n+ begin\n+ exit(IsolatedStorage.Contains(KeyName, DataScope::Module));\n+ end;\n+}\ndiff --git a/src/PlainTokenHeaderClient.Codeunit.al b/src/PlainTokenHeaderClient.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PlainTokenHeaderClient.Codeunit.al\n@@ -0,0 +1,18 @@\n+codeunit 50302 \"Plain Token Header Client\"\n+{\n+ procedure SendRequest(AccessToken: Text)\n+ var\n+ HttpClient: HttpClient;\n+ HttpHeaders: HttpHeaders;\n+ HttpResponseMessage: HttpResponseMessage;\n+ begin\n+ HttpHeaders := HttpClient.DefaultRequestHeaders();\n+ HttpHeaders.Add('Authorization', 'Bearer ' + AccessToken);\n+ HttpClient.Get(this.GetOrdersEndpoint(), HttpResponseMessage);\n+ end;\n+\n+ local procedure GetOrdersEndpoint(): Text\n+ begin\n+ exit('https://api.contoso.com/orders');\n+ end;\n+}\n", "expected_comments": [{"file": "src/PlainTokenHeaderClient.Codeunit.al", "line_start": 10, "line_end": 10, "severity": "high", "domain": "security", "body": "Bearer token is concatenated into a plain Text authorization header. Build the header with SecretStrSubstNo() and add it as SecretText."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "Clean codeunit correctly storing and retrieving API keys using IsolatedStorage.SetEncrypted, SecretText, and NonDebuggable", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-003", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/SafeErrorHandler.Codeunit.al b/src/SafeErrorHandler.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SafeErrorHandler.Codeunit.al\n@@ -0,0 +1,41 @@\n+codeunit 50102 \"Safe Error Handler\"\n+{\n+ Access = Internal;\n+\n+ var\n+ InvalidRequestTxt: Label 'Invalid request. Please check your input.';\n+ AuthFailedTxt: Label 'Authentication failed. Please verify your credentials.';\n+ ForbiddenTxt: Label 'You do not have permission for this operation.';\n+ NotFoundTxt: Label 'The requested resource was not found.';\n+ UnexpectedTxt: Label 'An unexpected error occurred. Contact your administrator.';\n+ PostFailedErr: Label 'Could not post document %1.', Comment = '%1 = document number';\n+\n+ procedure GetApiResponseMessage(StatusCode: Integer): Text\n+ begin\n+ case StatusCode of\n+ 200, 201:\n+ exit('');\n+ 400:\n+ exit(InvalidRequestTxt);\n+ 401:\n+ exit(AuthFailedTxt);\n+ 403:\n+ exit(ForbiddenTxt);\n+ 404:\n+ exit(NotFoundTxt);\n+ else\n+ exit(UnexpectedTxt);\n+ end;\n+ end;\n+\n+ procedure PostDocument(DocNo: Code[20])\n+ begin\n+ if not TryPost(DocNo) then\n+ Error(PostFailedErr, DocNo);\n+ end;\n+\n+ [TryFunction]\n+ local procedure TryPost(DocNo: Code[20])\n+ begin\n+ end;\n+}\ndiff --git a/src/UnwrappedSecretClient.Codeunit.al b/src/UnwrappedSecretClient.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/UnwrappedSecretClient.Codeunit.al\n@@ -0,0 +1,18 @@\n+codeunit 50303 \"Unwrapped Secret Client\"\n+{\n+ procedure SendRequest(SessionToken: SecretText)\n+ var\n+ HttpClient: HttpClient;\n+ HttpHeaders: HttpHeaders;\n+ HttpResponseMessage: HttpResponseMessage;\n+ begin\n+ HttpHeaders := HttpClient.DefaultRequestHeaders();\n+ HttpHeaders.Add('Authorization', this.BuildHeader(SessionToken));\n+ HttpClient.Get('https://api.contoso.com/data', HttpResponseMessage);\n+ end;\n+\n+ local procedure BuildHeader(SessionToken: SecretText): Text\n+ begin\n+ exit('Bearer ' + SessionToken.Unwrap());\n+ end;\n+}\n", "expected_comments": [{"file": "src/UnwrappedSecretClient.Codeunit.al", "line_start": 16, "line_end": 16, "severity": "high", "domain": "security", "body": "SecretText.Unwrap() exposes the secret as plain Text without a [NonDebuggable] procedure. Add [NonDebuggable] or avoid unwrapping by using SecretStrSubstNo()."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "Clean codeunit with proper error handling: generic user-facing messages, no system details exposed, no GetLastErrorText shown to user", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-004", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/AppConstants.Codeunit.al b/src/AppConstants.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/AppConstants.Codeunit.al\n@@ -0,0 +1,30 @@\n+codeunit 50103 \"App Constants\"\n+{\n+ Access = Internal;\n+\n+ var\n+ ApiVersionTok: Label 'v2.0', Locked = true;\n+ DefaultCurrencyTok: Label 'USD', Locked = true;\n+ DateFormatTok: Label 'yyyy-MM-dd', Locked = true;\n+ AppIdTok: Label 'BC-INVENTORY-APP', Locked = true;\n+\n+ procedure GetApiVersion(): Text\n+ begin\n+ exit(ApiVersionTok);\n+ end;\n+\n+ procedure GetDefaultCurrency(): Code[10]\n+ begin\n+ exit(DefaultCurrencyTok);\n+ end;\n+\n+ procedure GetDateFormat(): Text\n+ begin\n+ exit(DateFormatTok);\n+ end;\n+\n+ procedure GetAppId(): Text\n+ begin\n+ exit(AppIdTok);\n+ end;\n+}\ndiff --git a/src/QueryStringSecretClient.Codeunit.al b/src/QueryStringSecretClient.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/QueryStringSecretClient.Codeunit.al\n@@ -0,0 +1,15 @@\n+codeunit 50304 \"Query String Secret Client\"\n+{\n+ procedure FetchAccount(ApiKey: Text)\n+ var\n+ HttpClient: HttpClient;\n+ HttpResponseMessage: HttpResponseMessage;\n+ begin\n+ HttpClient.Get(this.BuildAccountUrl(ApiKey), HttpResponseMessage);\n+ end;\n+\n+ local procedure BuildAccountUrl(ApiKey: Text): Text\n+ begin\n+ exit('https://api.contoso.com/accounts?api_key=' + ApiKey);\n+ end;\n+}\n", "expected_comments": [{"file": "src/QueryStringSecretClient.Codeunit.al", "line_start": 13, "line_end": 13, "severity": "high", "domain": "security", "body": "API key is placed in the URL query string. Use an Authorization header, or SetSecretRequestUri() if a secret URI is unavoidable."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "Clean codeunit with configuration constants that are not secrets: API version, currency codes, labels, and format strings", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-005", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/ValidatedImportConfig.Table.al b/src/ValidatedImportConfig.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ValidatedImportConfig.Table.al\n@@ -0,0 +1,34 @@\n+table 50104 \"Validated Import Config\"\n+{\n+ Caption = 'Validated Import Configuration';\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"Code\"; Code[20])\n+ {\n+ Caption = 'Code';\n+ NotBlank = true;\n+ }\n+ field(2; \"Source Table ID\"; Integer)\n+ {\n+ Caption = 'Source Table';\n+ TableRelation = AllObjWithCaption.\"Object ID\" where(\"Object Type\" = const(Table));\n+ ValidateTableRelation = true;\n+ }\n+ field(3; \"Max Records\"; Integer)\n+ {\n+ Caption = 'Maximum Records';\n+ MinValue = 1;\n+ MaxValue = 10000;\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Code\")\n+ {\n+ Clustered = true;\n+ }\n+ }\n+}\ndiff --git a/src/BroadFinanceAccess.PermissionSet.al b/src/BroadFinanceAccess.PermissionSet.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/BroadFinanceAccess.PermissionSet.al\n@@ -0,0 +1,15 @@\n+permissionset 50305 \"Broad Finance Access\"\n+{\n+ Assignable = true;\n+ Caption = 'Broad Finance Access', Locked = true;\n+ Permissions =\n+ tabledata * = RIMD,\n+ table * = X,\n+ tabledata Customer = R,\n+ tabledata Vendor = R,\n+ tabledata Item = R,\n+ tabledata \"Sales Header\" = R,\n+ tabledata \"Sales Line\" = R,\n+ codeunit \"Release Sales Document\" = X,\n+ codeunit \"Sales-Post\" = X;\n+}\n", "expected_comments": [{"file": "src/BroadFinanceAccess.PermissionSet.al", "line_start": 6, "line_end": 6, "severity": "critical", "domain": "security", "body": "Permission set grants RIMD on all table data. Replace the wildcard with the minimum specific tabledata permissions required."}, {"file": "src/BroadFinanceAccess.PermissionSet.al", "line_start": 7, "line_end": 7, "severity": "high", "domain": "security", "body": "Permission set grants execute permission on all tables. Grant execute only on the specific objects this role requires."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "Clean table with proper input validation: ValidateTableRelation, OnValidate triggers, MinValue/MaxValue, Editable=false on system fields", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-006", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/InventoryReader.PermissionSet.al b/src/InventoryReader.PermissionSet.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/InventoryReader.PermissionSet.al\n@@ -0,0 +1,11 @@\n+permissionset 50106 \"Inventory Reader\"\n+{\n+ Caption = 'Inventory Reader';\n+ Assignable = true;\n+\n+ Permissions =\n+ tabledata Item = r,\n+ tabledata \"Item Ledger Entry\" = r,\n+ tabledata \"Item Category\" = r,\n+ codeunit \"Inventory Lookup\" = X;\n+}\ndiff --git a/src/InventoryLookup.Codeunit.al b/src/InventoryLookup.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/InventoryLookup.Codeunit.al\n@@ -0,0 +1,15 @@\n+codeunit 50108 \"Inventory Lookup\"\n+{\n+ Access = Internal;\n+ Permissions = tabledata Item = r;\n+\n+ procedure GetItemDescription(ItemNo: Code[20]): Text[100]\n+ var\n+ Item: Record Item;\n+ begin\n+ Item.SetLoadFields(Description);\n+ if Item.Get(ItemNo) then\n+ exit(Item.Description);\n+ exit('');\n+ end;\n+}\ndiff --git a/src/ExcessiveInherentAccess.Codeunit.al b/src/ExcessiveInherentAccess.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExcessiveInherentAccess.Codeunit.al\n@@ -0,0 +1,17 @@\n+codeunit 50306 \"Excessive Inherent Access\"\n+{\n+ procedure LookupCustomerName(CustomerNo: Code[20]): Text\n+ begin\n+ exit(this.GetCustomerName(CustomerNo));\n+ end;\n+\n+ [InherentPermissions(PermissionObjectType::TableData, Database::Customer, 'RIMD')]\n+ local procedure GetCustomerName(CustomerNo: Code[20]): Text\n+ var\n+ Customer: Record Customer;\n+ begin\n+ if Customer.Get(CustomerNo) then\n+ exit(Customer.Name);\n+ exit('');\n+ end;\n+}\n", "expected_comments": [{"file": "src/ExcessiveInherentAccess.Codeunit.al", "line_start": 8, "line_end": 8, "severity": "high", "domain": "security", "body": "InherentPermissions grants RIMD tabledata access even though this procedure only reads Customer. Reduce the permission to the minimal read access required."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "Clean permission sets with least-privilege access: read-only for readers, read-insert for editors, no RIMD grants", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-007", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/SafeRecordQuery.Codeunit.al b/src/SafeRecordQuery.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SafeRecordQuery.Codeunit.al\n@@ -0,0 +1,31 @@\n+codeunit 50109 \"Safe Record Query\"\n+{\n+ Access = Internal;\n+\n+ procedure CustomerExists(CustomerNo: Code[20]): Boolean\n+ var\n+ Customer: Record Customer;\n+ begin\n+ Customer.SetLoadFields(\"No.\");\n+ exit(Customer.Get(CustomerNo));\n+ end;\n+\n+ procedure HasOpenSalesOrders(CustomerNo: Code[20]): Boolean\n+ var\n+ SalesHeader: Record \"Sales Header\";\n+ begin\n+ SalesHeader.SetRange(\"Document Type\", SalesHeader.\"Document Type\"::Order);\n+ SalesHeader.SetRange(\"Sell-to Customer No.\", CustomerNo);\n+ exit(not SalesHeader.IsEmpty());\n+ end;\n+\n+ procedure GetItemDescription(ItemNo: Code[20]): Text[100]\n+ var\n+ Item: Record Item;\n+ begin\n+ Item.SetLoadFields(Description);\n+ if Item.Get(ItemNo) then\n+ exit(Item.Description);\n+ exit('');\n+ end;\n+}\ndiff --git a/src/InsecureEndpointClient.Codeunit.al b/src/InsecureEndpointClient.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/InsecureEndpointClient.Codeunit.al\n@@ -0,0 +1,17 @@\n+codeunit 50307 \"Insecure Endpoint Client\"\n+{\n+ procedure SendSession(SessionToken: SecretText)\n+ var\n+ HttpClient: HttpClient;\n+ HttpContent: HttpContent;\n+ HttpResponseMessage: HttpResponseMessage;\n+ begin\n+ HttpContent.WriteFrom(SessionToken);\n+ HttpClient.Post(this.GetEndpoint(), HttpContent, HttpResponseMessage);\n+ end;\n+\n+ local procedure GetEndpoint(): Text\n+ begin\n+ exit('http://api.contoso.com/session');\n+ end;\n+}\n", "expected_comments": [{"file": "src/InsecureEndpointClient.Codeunit.al", "line_start": 15, "line_end": 15, "severity": "high", "domain": "security", "body": "External service endpoint uses HTTP instead of HTTPS. Use HTTPS for all external HTTP calls, especially when sending session tokens."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "Clean codeunit using proper BC record operations: SetRange, SetFilter, FindSet, Count — no string concatenation or dynamic SQL", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-008", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/PartnerConfigKeyManager.Codeunit.al b/src/PartnerConfigKeyManager.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PartnerConfigKeyManager.Codeunit.al\n@@ -0,0 +1,9 @@\n+codeunit 50100 \"Partner Config Key Manager\"\n+{\n+ Access = Internal;\n+\n+ internal procedure StoreApiKey(KeyName: Text; ApiKey: Text)\n+ begin\n+ IsolatedStorage.SetEncrypted(KeyName, ApiKey, DataScope::Module);\n+ end;\n+}\n", "expected_comments": [{"file": "src/PartnerConfigKeyManager.Codeunit.al", "line_start": 5, "line_end": 5, "domain": "security", "severity": "high", "body": "The API key is accepted as a plain Text parameter instead of SecretText, so the secret is exposed in memory and to anyone inspecting the call stack or debugger."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "True positive security findings: encryption (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-009", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/OutlookAddinDeployer.Codeunit.al b/src/OutlookAddinDeployer.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/OutlookAddinDeployer.Codeunit.al\n@@ -0,0 +1,37 @@\n+namespace Microsoft.Integration.Outlook;\n+\n+codeunit 50104 \"Outlook Addin Deployer\"\n+{\n+ Access = Internal;\n+\n+ var\n+ EndpointTok: Label 'https://outlook.office365.com/api/v2.0/addins/deploy', Locked = true;\n+ StatusErr: Label 'Deployment failed (HTTP %1): %2', Comment = '%1 is the HTTP status code, %2 is the raw response body.';\n+ ConnectErr: Label 'Failed to connect to the deployment service: %1', Comment = '%1 is the underlying error text.';\n+\n+ procedure DeployAddin(ManifestPath: Text)\n+ var\n+ Client: HttpClient;\n+ Content: HttpContent;\n+ Response: HttpResponseMessage;\n+ Headers: HttpHeaders;\n+ Payload: Text;\n+ ResponseText: Text;\n+ begin\n+ Payload := '{\"manifest\":\"' + ManifestPath + '\"}';\n+ Content.WriteFrom(Payload);\n+ Content.GetHeaders(Headers);\n+ Headers.Add('Authorization', 'Bearer ' + GetAccessToken());\n+ if Client.Post(EndpointTok, Content, Response) then begin\n+ Response.Content.ReadAs(ResponseText);\n+ if not Response.IsSuccessStatusCode() then\n+ Error(StatusErr, Response.HttpStatusCode(), ResponseText);\n+ end else\n+ Error(ConnectErr, GetLastErrorText());\n+ end;\n+\n+ local procedure GetAccessToken(): Text\n+ begin\n+ exit('dummy_access_token_for_testing');\n+ end;\n+}\n", "expected_comments": [{"file": "src/OutlookAddinDeployer.Codeunit.al", "line_start": 21, "line_end": 21, "domain": "security", "severity": "medium", "body": "The manifest path is concatenated directly into a JSON payload, allowing JSON injection. Build the payload with a JsonObject so values are escaped."}, {"file": "src/OutlookAddinDeployer.Codeunit.al", "line_start": 28, "line_end": 28, "domain": "security", "severity": "medium", "body": "The error surfaces the raw HTTP status code and full response body to the user, leaking internal service details. Log the details and show a generic message."}, {"file": "src/OutlookAddinDeployer.Codeunit.al", "line_start": 30, "line_end": 30, "domain": "security", "severity": "medium", "body": "GetLastErrorText() is shown to the user, exposing internal system details. Log the raw error and present a sanitized message."}, {"file": "src/OutlookAddinDeployer.Codeunit.al", "line_start": 33, "line_end": 33, "domain": "security", "severity": "high", "body": "The access token is returned as plain Text instead of SecretText, exposing it in memory and to the debugger. Return and handle it as SecretText."}, {"file": "src/OutlookAddinDeployer.Codeunit.al", "line_start": 35, "line_end": 35, "domain": "security", "severity": "high", "body": "A hardcoded access token is embedded in source code. Retrieve the token from a secure store or OAuth flow instead of hardcoding it."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "True positive security findings: error_exposure (verified line numbers)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-010", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/PaymentGatewayConnector.Codeunit.al b/src/PaymentGatewayConnector.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PaymentGatewayConnector.Codeunit.al\n@@ -0,0 +1,44 @@\n+codeunit 50300 \"Payment Gateway Connector\"\n+{\n+ Access = Internal;\n+\n+ var\n+ ConnectionFailedErr: Label 'Payment gateway connection failed.', Comment = 'Shown when the payment gateway cannot be reached.';\n+ GatewaySecretLbl: Label 'my-secret-api-key-do-not-commit-this', Comment = 'Fallback gateway credential used when no stored secret is configured.';\n+\n+ procedure InitializeGateway()\n+ var\n+ ApiKey: Text;\n+ begin\n+ ApiKey := 'hardcoded-api-key-value-12345';\n+ SetupGatewayConnection(ApiKey);\n+ end;\n+\n+ procedure GetStoredCredential(KeyName: Text): Text\n+ var\n+ KeyValue: Text;\n+ begin\n+ if IsolatedStorage.Contains(KeyName, DataScope::Module) then\n+ IsolatedStorage.Get(KeyName, DataScope::Module, KeyValue);\n+ exit(KeyValue);\n+ end;\n+\n+ procedure StoreCredential(KeyName: Text; KeyValue: Text)\n+ begin\n+ IsolatedStorage.SetEncrypted(KeyName, KeyValue, DataScope::Module);\n+ end;\n+\n+ local procedure SetupGatewayConnection(ApiKey: Text)\n+ var\n+ Client: HttpClient;\n+ Headers: HttpHeaders;\n+ Response: HttpResponseMessage;\n+ begin\n+ Headers := Client.DefaultRequestHeaders();\n+ Headers.Add('Authorization', 'Basic ' + ApiKey);\n+ Headers.Add('X-Api-Secret', GatewaySecretLbl);\n+ Client.Get('https://api.paymentgateway.com/v1/setup', Response);\n+ if not Response.IsSuccessStatusCode() then\n+ Error(ConnectionFailedErr);\n+ end;\n+}\n", "expected_comments": [{"file": "src/PaymentGatewayConnector.Codeunit.al", "line_start": 7, "line_end": 7, "domain": "security", "severity": "critical", "body": "API secret is embedded in a Label constant. Secrets must never be stored in source; retrieve them from IsolatedStorage or a secure key vault."}, {"file": "src/PaymentGatewayConnector.Codeunit.al", "line_start": 13, "line_end": 13, "domain": "security", "severity": "critical", "body": "Hardcoded API key assigned directly in code. Retrieve the API key from IsolatedStorage or a secure key vault instead of embedding it."}, {"file": "src/PaymentGatewayConnector.Codeunit.al", "line_start": 22, "line_end": 22, "domain": "security", "severity": "high", "body": "Credentials are written with SetEncrypted but read back with a plain Get, so the value is never decrypted. Use GetEncrypted to match the encrypted write."}, {"file": "src/PaymentGatewayConnector.Codeunit.al", "line_start": 38, "line_end": 38, "domain": "security", "severity": "high", "body": "API key is handled as plain Text and concatenated into a Basic Authorization header without base64 encoding. Use SecretText and encode credentials correctly."}, {"file": "src/PaymentGatewayConnector.Codeunit.al", "line_start": 40, "line_end": 40, "domain": "security", "severity": "high", "body": "The boolean return value of HttpClient.Get is ignored, so a transport-level failure is treated as success. Check the return value before reading the response."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "True positive security findings: hardcoded_credentials — hardcoded API key, secret in Label, public IsolatedStorage access", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-011", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/ElecVATSubmission.Codeunit.al b/src/ElecVATSubmission.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ElecVATSubmission.Codeunit.al\n@@ -0,0 +1,24 @@\n+namespace Microsoft.Finance.VAT;\n+\n+codeunit 13610 \"Elec VAT Submission\"\n+{\n+ Access = Internal;\n+\n+ procedure SubmitReturn(AuthorityUrl: Text): Boolean\n+ var\n+ Client: HttpClient;\n+ Content: HttpContent;\n+ Response: HttpResponseMessage;\n+ begin\n+ Content.WriteFrom('{}');\n+ exit(Client.Post(AuthorityUrl, Content, Response));\n+ end;\n+\n+ procedure CheckHealth(ServiceUrl: Text): Boolean\n+ var\n+ Client: HttpClient;\n+ Response: HttpResponseMessage;\n+ begin\n+ exit(Client.Get(ServiceUrl, Response));\n+ end;\n+}\n", "expected_comments": [{"file": "src/ElecVATSubmission.Codeunit.al", "line_start": 14, "line_end": 14, "domain": "security", "severity": "high", "body": "A caller-supplied URL is passed to HttpClient.Post without host or scheme validation, allowing SSRF requests to internal or attacker-chosen endpoints."}, {"file": "src/ElecVATSubmission.Codeunit.al", "line_start": 22, "line_end": 22, "domain": "security", "severity": "high", "body": "A caller-supplied URL is passed to HttpClient.Get without host or scheme validation, allowing SSRF requests to internal or attacker-chosen endpoints."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "True positive security findings: input_validation (trimmed to core input validation cases)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-012", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/HttpAuthenticationBasic.Codeunit.al b/src/HttpAuthenticationBasic.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/HttpAuthenticationBasic.Codeunit.al\n@@ -0,0 +1,37 @@\n+codeunit 2359 \"Http Authentication Basic\"\n+{\n+ Access = Public;\n+ InherentEntitlements = X;\n+ InherentPermissions = X;\n+\n+ var\n+ Credential: SecretText;\n+ UsernameDomainTok: Label '%1\\%2', Comment = '%1 = domain, %2 = user name', Locked = true;\n+\n+ [NonDebuggable]\n+ procedure Initialize(Username: SecretText; Domain: Text; Password: SecretText)\n+ begin\n+ Credential := SecretStrSubstNo('%1:%2', QualifyUser(Username, Domain), Password);\n+ end;\n+\n+ procedure GetAuthorizationHeader() Header: SecretText\n+ begin\n+ Header := ToBase64(Credential);\n+ end;\n+\n+ [NonDebuggable]\n+ local procedure QualifyUser(Username: SecretText; Domain: Text): SecretText\n+ begin\n+ if Domain = '' then\n+ exit(Username);\n+ exit(SecretStrSubstNo(UsernameDomainTok, Domain, Username));\n+ end;\n+\n+ local procedure ToBase64(Value: SecretText) Base64Value: SecretText\n+ var\n+ Convert: DotNet Convert;\n+ Encoding: DotNet Encoding;\n+ begin\n+ Base64Value := Convert.ToBase64String(Encoding.UTF8().GetBytes(Value.Unwrap()));\n+ end;\n+}\n", "expected_comments": [{"file": "src/HttpAuthenticationBasic.Codeunit.al", "line_start": 30, "line_end": 30, "domain": "security", "severity": "medium", "body": "ToBase64 transforms SecretText credential material and calls Unwrap() without [NonDebuggable], so the plaintext credential is visible in the debugger."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "True positive security findings: procedures handling passwords or SecretText values without [NonDebuggable]", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-013", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/ExpenseAgentAdmin.PermissionSet.al b/src/ExpenseAgentAdmin.PermissionSet.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExpenseAgentAdmin.PermissionSet.al\n@@ -0,0 +1,15 @@\n+// ------------------------------------------------------------------------------------------------\n+// Copyright (c) Microsoft Corporation. All rights reserved.\n+// Licensed under the MIT License. See License.txt in the project root for license information.\n+// ------------------------------------------------------------------------------------------------\n+namespace Microsoft.Agents.Expense;\n+\n+permissionset 50700 \"Expense Agent Admin\"\n+{\n+ Assignable = true;\n+ Caption = 'Expense Agent Administration';\n+\n+ Permissions =\n+ tabledata \"Agent Creation Control\" = RIMD,\n+ tabledata \"Expense Report Rule Violation\" = IMD;\n+}\ndiff --git a/src/ExpenseAgentConsumption.Table.al b/src/ExpenseAgentConsumption.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExpenseAgentConsumption.Table.al\n@@ -0,0 +1,51 @@\n+// ------------------------------------------------------------------------------------------------\n+// Copyright (c) Microsoft Corporation. All rights reserved.\n+// Licensed under the MIT License. See License.txt in the project root for license information.\n+// ------------------------------------------------------------------------------------------------\n+namespace Microsoft.Agents.Expense;\n+\n+table 50600 \"Expense Agent Consumption\"\n+{\n+ Caption = 'Expense Agent Consumption';\n+ DataClassification = CustomerContent;\n+ InherentEntitlements = RIX;\n+ InherentPermissions = RIX;\n+\n+ fields\n+ {\n+ field(1; \"Entry No.\"; Integer)\n+ {\n+ Caption = 'Entry No.';\n+ DataClassification = SystemMetadata;\n+ AutoIncrement = true;\n+ }\n+ field(10; Amount; Decimal)\n+ {\n+ Caption = 'Amount';\n+ DataClassification = CustomerContent;\n+ }\n+ field(20; \"User Security ID\"; Guid)\n+ {\n+ Caption = 'User Security ID';\n+ DataClassification = EndUserPseudonymousIdentifiers;\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Entry No.\")\n+ {\n+ Clustered = true;\n+ }\n+ }\n+\n+ procedure LogConsumption(CallerSecurityId: Guid; ConsumptionAmount: Decimal)\n+ var\n+ ConsumptionEntry: Record \"Expense Agent Consumption\";\n+ begin\n+ ConsumptionEntry.Init();\n+ ConsumptionEntry.\"User Security ID\" := CallerSecurityId;\n+ ConsumptionEntry.Amount := ConsumptionAmount;\n+ ConsumptionEntry.Insert();\n+ end;\n+}\n", "expected_comments": [{"file": "src/ExpenseAgentConsumption.Table.al", "line_start": 11, "line_end": 11, "domain": "security", "severity": "medium", "body": "InherentEntitlements and InherentPermissions of RIX grant read/insert/execute to every user regardless of assigned permission sets. Remove the inherent grants and control access explicitly."}, {"file": "src/ExpenseAgentConsumption.Table.al", "line_start": 42, "line_end": 42, "domain": "security", "severity": "medium", "body": "The procedure accepts an arbitrary UserSecurityId, letting a caller log consumption against any user's identity. Derive the user from UserSecurityId() instead of trusting the parameter."}, {"file": "src/ExpenseAgentAdmin.PermissionSet.al", "line_start": 13, "line_end": 13, "domain": "security", "severity": "medium", "body": "RIMD on Agent Creation Control lets assigned users delete creation-control records, removing a security guardrail. Grant only the permissions actually required."}, {"file": "src/ExpenseAgentAdmin.PermissionSet.al", "line_start": 14, "line_end": 14, "domain": "security", "severity": "medium", "body": "IMD on Expense Report Rule Violation lets users delete recorded policy violations, enabling them to hide their own violations. Remove delete and modify access."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "True positive security findings: permission (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-014", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/SecureOperationHelper.Codeunit.al b/src/SecureOperationHelper.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SecureOperationHelper.Codeunit.al\n@@ -0,0 +1,13 @@\n+codeunit 50105 \"Secure Operation Helper\"\n+{\n+ Access = Internal;\n+\n+ internal procedure DeleteAllRecords(TableNo: Integer)\n+ var\n+ RecRef: RecordRef;\n+ begin\n+ RecRef.Open(TableNo);\n+ RecRef.DeleteAll();\n+ RecRef.Close();\n+ end;\n+}\n", "expected_comments": [{"file": "src/SecureOperationHelper.Codeunit.al", "line_start": 9, "line_end": 10, "domain": "security", "severity": "high", "body": "A caller-provided table number is opened with RecordRef.Open and then DeleteAll is called, letting any caller delete every record in an arbitrary table. Restrict the allowed tables and enforce permission checks."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "True positive: public procedure uses RecordRef.Open with caller-provided table number, allowing any extension to delete all records from any table through this codeunit's permissions", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-015", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/ExternalIntegrationMgt.Codeunit.al b/src/ExternalIntegrationMgt.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExternalIntegrationMgt.Codeunit.al\n@@ -0,0 +1,24 @@\n+namespace Microsoft.Integration.Partner;\n+\n+codeunit 50205 \"External Integration Mgt.\"\n+{\n+ Access = Internal;\n+\n+ procedure PostToPartner(EndpointUrl: Text): Boolean\n+ var\n+ Client: HttpClient;\n+ Content: HttpContent;\n+ Response: HttpResponseMessage;\n+ begin\n+ Content.WriteFrom('{}');\n+ exit(Client.Post(EndpointUrl, Content, Response));\n+ end;\n+\n+ procedure GetFromProvider(EndpointUrl: Text): Boolean\n+ var\n+ Client: HttpClient;\n+ Response: HttpResponseMessage;\n+ begin\n+ exit(Client.Get(EndpointUrl, Response));\n+ end;\n+}\n", "expected_comments": [{"file": "src/ExternalIntegrationMgt.Codeunit.al", "line_start": 14, "line_end": 14, "domain": "security", "severity": "high", "body": "A caller-supplied URL is passed to HttpClient.Post without host or scheme validation, allowing SSRF requests to internal or attacker-chosen endpoints."}, {"file": "src/ExternalIntegrationMgt.Codeunit.al", "line_start": 22, "line_end": 22, "domain": "security", "severity": "high", "body": "A caller-supplied URL is passed to HttpClient.Get without host or scheme validation, allowing SSRF requests to internal or attacker-chosen endpoints."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "True positive security findings: URLs from table fields used in HTTP requests without validation (SSRF risk). Three procedures use user-configurable URLs directly, while two procedures correctly validate using Uri.AreURIsHaveSameHost and Uri.IsValidURIPattern.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__security-016", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "security"}, "patch": "diff --git a/src/ExpenseHtmlNotifier.Codeunit.al b/src/ExpenseHtmlNotifier.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExpenseHtmlNotifier.Codeunit.al\n@@ -0,0 +1,12 @@\n+codeunit 50900 \"Expense Html Notifier\"\n+{\n+ Access = Internal;\n+\n+ var\n+ BodyTemplateTok: Label '

Dear %1,

%2

', Locked = true;\n+\n+ internal procedure BuildNotificationBody(EmployeeName: Text; Description: Text): Text\n+ begin\n+ exit(StrSubstNo(BodyTemplateTok, EmployeeName, Description));\n+ end;\n+}\n", "expected_comments": [{"file": "src/ExpenseHtmlNotifier.Codeunit.al", "line_start": 10, "line_end": 10, "domain": "security", "severity": "high", "body": "User-supplied EmployeeName and Description are substituted into the HTML body without encoding, enabling stored or reflected XSS. HTML-encode the values before embedding them."}], "match_line_tolerance": 2, "domain": "security", "category": "code-review", "description": "True positive security findings: xss (user-supplied data embedded in HTML without encoding)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-001", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/FADepreciationBook.Table.al b/src/FADepreciationBook.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/FADepreciationBook.Table.al\n@@ -0,0 +1,78 @@\n+table 50200 \"FA Depreciation Book FP\"\n+{\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"FA No.\"; Code[20])\n+ {\n+ Caption = 'FA No.';\n+ TableRelation = \"Fixed Asset\";\n+ }\n+\n+ field(2; \"Depreciation Book Code\"; Code[10])\n+ {\n+ Caption = 'Depreciation Book Code';\n+ TableRelation = \"Depreciation Book\";\n+ }\n+\n+ field(3; Depreciation; Decimal)\n+ {\n+ FieldClass = FlowField;\n+ CalcFormula = sum(\"FA Ledger Entry\".Amount where(\"FA No.\" = field(\"FA No.\"),\n+ \"Depreciation Book Code\" = field(\"Depreciation Book Code\"),\n+ \"FA Posting Category\" = const(Depreciation)));\n+ Caption = 'Depreciation';\n+ }\n+\n+ field(4; \"Bonus Depr. Applied Amount\"; Decimal)\n+ {\n+ FieldClass = FlowField;\n+ CalcFormula = sum(\"FA Ledger Entry\".Amount where(\"FA No.\" = field(\"FA No.\"),\n+ \"Depreciation Book Code\" = field(\"Depreciation Book Code\"),\n+ \"FA Posting Type\" = const(\"Bonus Depreciation\")));\n+ Caption = 'Bonus Depr. Applied Amount';\n+ }\n+\n+ field(5; \"Use Half-Year Convention\"; Boolean)\n+ {\n+ Caption = 'Use Half-Year Convention';\n+\n+ trigger OnValidate()\n+ var\n+ CannotChangeHalfYearErr: Label 'Cannot change half-year convention after depreciation has been posted.';\n+ CannotChangeBonusErr: Label 'Cannot change half-year convention when bonus depreciation has been applied.';\n+ begin\n+ // CORRECT: CalcFields in OnValidate runs once per user edit, not in a loop\n+ // This is appropriate for validation logic that needs current flowfield values\n+ CalcFields(Depreciation);\n+ if Depreciation <> 0 then\n+ Error(CannotChangeHalfYearErr);\n+\n+ CalcFields(\"Bonus Depr. Applied Amount\");\n+ if \"Bonus Depr. Applied Amount\" <> 0 then\n+ Error(CannotChangeBonusErr);\n+ end;\n+ }\n+\n+ field(6; \"Depreciation Method\"; Option)\n+ {\n+ OptionCaption = 'Straight-Line,Declining-Balance 1,Declining-Balance 2';\n+ OptionMembers = \"Straight-Line\",\"Declining-Balance 1\",\"Declining-Balance 2\";\n+ Caption = 'Depreciation Method';\n+ }\n+\n+ field(7; \"Starting Date\"; Date)\n+ {\n+ Caption = 'Depreciation Starting Date';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"FA No.\", \"Depreciation Book Code\")\n+ {\n+ Clustered = true;\n+ }\n+ }\n+}\ndiff --git a/src/SalesOrderCard.Page.al b/src/SalesOrderCard.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SalesOrderCard.Page.al\n@@ -0,0 +1,87 @@\n+page 50201 \"Sales Order Card FP\"\n+{\n+ PageType = Card;\n+ SourceTable = \"Sales Header\";\n+ Caption = 'Sales Order Card FP';\n+\n+ layout\n+ {\n+ area(Content)\n+ {\n+ group(General)\n+ {\n+ Caption = 'General';\n+\n+ field(\"No.\"; Rec.\"No.\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the number of the sales order.';\n+ }\n+\n+ field(\"Sell-to Customer No.\"; Rec.\"Sell-to Customer No.\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the number of the customer who will receive the products on the sales order.';\n+ }\n+\n+ field(\"Document Date\"; Rec.\"Document Date\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the date when the sales order was created.';\n+ }\n+\n+ field(\"Total Amount\"; TotalAmount)\n+ {\n+ Caption = 'Total Amount Including VAT';\n+ ApplicationArea = All;\n+ Editable = false;\n+ ToolTip = 'Specifies the total amount including VAT for the sales order.';\n+ }\n+ }\n+ }\n+ }\n+\n+ actions\n+ {\n+ area(Processing)\n+ {\n+ action(RefreshTotals)\n+ {\n+ Caption = 'Refresh Totals';\n+ ApplicationArea = All;\n+ ToolTip = 'Recalculates and refreshes the total amount for the sales order.';\n+ Image = Refresh;\n+\n+ trigger OnAction()\n+ var\n+ TotalRefreshedMsg: Label 'Total refreshed: %1', Comment = '%1 = total amount including VAT';\n+ begin\n+ // CORRECT: Manual refresh action - user-initiated, runs once\n+ Rec.CalcFields(\"Amount Including VAT\");\n+ TotalAmount := Rec.\"Amount Including VAT\";\n+ Message(TotalRefreshedMsg, TotalAmount);\n+ end;\n+ }\n+ }\n+ }\n+\n+ var\n+ TotalAmount: Decimal;\n+\n+ // CORRECT: OnAfterGetCurrRecord fires once per record selection, not per row\n+ // This is the appropriate place to calculate values when user navigates to a record\n+ trigger OnAfterGetCurrRecord()\n+ begin\n+ // Calculate total amount when user selects a sales order\n+ // This runs once when the record is loaded/selected, not in a loop\n+ Rec.CalcFields(\"Amount Including VAT\");\n+ TotalAmount := Rec.\"Amount Including VAT\";\n+ end;\n+\n+ trigger OnNewRecord(BelowxRec: Boolean)\n+ begin\n+ // CORRECT: Initialize values for new record - runs once per new record creation\n+ TotalAmount := 0;\n+ Rec.\"Document Date\" := WorkDate();\n+ end;\n+}\ndiff --git a/src/CustLedgerEntryAggregator.Codeunit.al b/src/CustLedgerEntryAggregator.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustLedgerEntryAggregator.Codeunit.al\n@@ -0,0 +1,20 @@\n+codeunit 50202 \"Cust Ledger Entry Aggregator\"\n+{\n+ procedure SumOpenRemainingAmount(CustomerNo: Code[20]): Decimal\n+ var\n+ CustLedgerEntry: Record \"Cust. Ledger Entry\";\n+ Customer: Record Customer;\n+ Total: Decimal;\n+ begin\n+ CustLedgerEntry.SetRange(\"Customer No.\", CustomerNo);\n+ CustLedgerEntry.SetRange(Open, true);\n+ if CustLedgerEntry.FindSet() then\n+ repeat\n+ CustLedgerEntry.CalcFields(\"Remaining Amount\");\n+ Customer.Get(CustLedgerEntry.\"Customer No.\");\n+ if Customer.\"Application Method\" = Customer.\"Application Method\"::Manual then\n+ Total += CustLedgerEntry.\"Remaining Amount\";\n+ until CustLedgerEntry.Next() = 0;\n+ exit(Total);\n+ end;\n+}\n", "expected_comments": [{"file": "src/CustLedgerEntryAggregator.Codeunit.al", "line_start": 13, "line_end": 13, "body": "CalcFields(\"Remaining Amount\") inside a repeat..until loop over \"Cust. Ledger Entry\" (up to 10M rows) issues one SQL query per iteration — classic N+1 against a hot table. — Replace the loop with `CustLedgerEntry.CalcSums(\"Remaining Amount\")` which executes as a single SUM query, or use a SIFT-backed key.", "severity": "high", "domain": "performance"}, {"file": "src/CustLedgerEntryAggregator.Codeunit.al", "line_start": 14, "line_end": 14, "body": "Customer.Get(CustLedgerEntry.\"Customer No.\") inside a repeat..until over Cust. Ledger Entry is an N+1 query: one Customer lookup per ledger row, redundant since every row already has the same Customer No. (the loop is filtered by CustomerNo). — Move the Customer.Get above the loop (single lookup), and add `Customer.SetLoadFields(\"Application Method\")` since only one field is read.", "severity": "high", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "False positive performance findings: calcfields_false_positive (30 false positives). Agent flagged these but reviewers rejected them. Enriched with 2 true-positive findings in addition to the false-positive bait.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-002", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/SetupReader.Codeunit.al b/src/SetupReader.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SetupReader.Codeunit.al\n@@ -0,0 +1,67 @@\n+codeunit 50211 \"Setup Reader\"\n+{\n+ procedure GetSetupValues()\n+ var\n+ GLSetup: Record \"General Ledger Setup\";\n+ SalesSetup: Record \"Sales & Receivables Setup\";\n+ InventorySetup: Record \"Inventory Setup\";\n+ PurchSetup: Record \"Purchases & Payables Setup\";\n+ begin\n+ // CORRECT: Setup tables typically have only 1 record per company\n+ // Any access pattern (Get, FindSet, FindFirst) is fine for singleton tables\n+ GLSetup.Get();\n+ SalesSetup.Get();\n+ InventorySetup.Get();\n+ PurchSetup.Get();\n+\n+ if GLSetup.\"Additional Reporting Currency\" <> '' then\n+ ProcessACYSettings(GLSetup);\n+\n+ if SalesSetup.\"Credit Warnings\" <> SalesSetup.\"Credit Warnings\"::\"No Warning\" then\n+ EnableCreditWarnings(SalesSetup);\n+ end;\n+\n+ procedure ValidateCompanySettings(): Boolean\n+ var\n+ CompanyInfo: Record \"Company Information\";\n+ begin\n+ // CORRECT: Company Information is a singleton table (1 record per company)\n+ // Get() is the appropriate method for singleton tables\n+ if not CompanyInfo.Get() then\n+ exit(false);\n+\n+ if CompanyInfo.Name = '' then\n+ exit(false);\n+\n+ if CompanyInfo.\"Country/Region Code\" = '' then\n+ exit(false);\n+\n+ exit(true);\n+ end;\n+\n+ procedure GetUserSetupForCurrentUser(var UserSetup: Record \"User Setup\"): Boolean\n+ begin\n+ // CORRECT: Looking up single user's setup record\n+ // Get() with UserId is appropriate for single-record lookup\n+ UserSetup.Reset();\n+ if UserSetup.Get(UserId) then\n+ exit(true);\n+ exit(false);\n+ end;\n+\n+ local procedure ProcessACYSettings(GLSetup: Record \"General Ledger Setup\")\n+ var\n+ ACYEnabledMsg: Label 'ACY is enabled: %1', Comment = '%1 = additional reporting currency';\n+ begin\n+ // Process additional currency settings\n+ Message(ACYEnabledMsg, GLSetup.\"Additional Reporting Currency\");\n+ end;\n+\n+ local procedure EnableCreditWarnings(SalesSetup: Record \"Sales & Receivables Setup\")\n+ var\n+ CreditWarningsEnabledMsg: Label 'Credit warnings are enabled';\n+ begin\n+ // Enable credit warning processing\n+ Message(CreditWarningsEnabledMsg);\n+ end;\n+}\ndiff --git a/src/TempBufferProcessor.Codeunit.al b/src/TempBufferProcessor.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/TempBufferProcessor.Codeunit.al\n@@ -0,0 +1,61 @@\n+codeunit 50210 \"Temp Buffer Processor\"\n+{\n+ procedure ProcessBufferEntries(var TempBuffer: Record \"Integer\" temporary)\n+ var\n+ ProcessedCount: Integer;\n+ TotalAmount: Decimal;\n+ ProcessedEntriesMsg: Label 'Processed %1 entries with total %2', Comment = '%1 = number of entries, %2 = total amount';\n+ begin\n+ // CORRECT: TempBuffer is temporary — all operations are in-memory, no SQL queries\n+ // Any access pattern (FindSet, Get, loops) on temp tables is performant\n+ ProcessedCount := 0;\n+ TotalAmount := 0;\n+\n+ if TempBuffer.FindSet() then\n+ repeat\n+ // This might look suspicious, but it's CORRECT because:\n+ // 1. TempBuffer is temporary (in-memory)\n+ // 2. No database round trips are happening\n+ // 3. All data is already loaded in memory\n+ TotalAmount += TempBuffer.Number;\n+ ProcessedCount += 1;\n+\n+ // Even modifying temp records in a loop is fine\n+ TempBuffer.Number := TempBuffer.Number * 2;\n+ TempBuffer.Modify();\n+\n+ until TempBuffer.Next() = 0;\n+\n+ Message(ProcessedEntriesMsg, ProcessedCount, TotalAmount);\n+ end;\n+\n+ procedure BuildTempData(var TempBuffer: Record \"Integer\" temporary)\n+ var\n+ i: Integer;\n+ begin\n+ // CORRECT: Building temp data - all operations are in-memory\n+ TempBuffer.Reset();\n+ TempBuffer.DeleteAll();\n+\n+ for i := 1 to 100 do begin\n+ TempBuffer.Init();\n+ TempBuffer.Number := Random(1000);\n+ TempBuffer.Insert();\n+ end;\n+ end;\n+\n+ procedure FindMaxValue(var TempBuffer: Record \"Integer\" temporary): Integer\n+ var\n+ MaxValue: Integer;\n+ begin\n+ // CORRECT: Finding max in temp table - no performance concern\n+ MaxValue := 0;\n+ if TempBuffer.FindSet() then\n+ repeat\n+ if TempBuffer.Number > MaxValue then\n+ MaxValue := TempBuffer.Number;\n+ until TempBuffer.Next() = 0;\n+\n+ exit(MaxValue);\n+ end;\n+}\ndiff --git a/src/CustomerLookup.Codeunit.al b/src/CustomerLookup.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerLookup.Codeunit.al\n@@ -0,0 +1,20 @@\n+codeunit 50212 \"Customer Lookup\"\n+{\n+ procedure GetCustomerName(CustomerNo: Code[20]): Text[100]\n+ var\n+ Customer: Record Customer;\n+ begin\n+ Customer.SetRange(\"No.\", CustomerNo);\n+ if Customer.FindFirst() then\n+ exit(Customer.Name);\n+ exit('');\n+ end;\n+\n+ procedure HasCustomersInCountry(CountryRegionCode: Code[10]): Boolean\n+ var\n+ Customer: Record Customer;\n+ begin\n+ Customer.SetRange(\"Country/Region Code\", CountryRegionCode);\n+ exit(Customer.Count() > 0);\n+ end;\n+}\n", "expected_comments": [{"file": "src/CustomerLookup.Codeunit.al", "line_start": 8, "line_end": 8, "body": "FindFirst() after SetRange on the full primary key (\"No.\") of Customer (up to 800k rows). This still does a SQL SELECT TOP 1 with a range predicate instead of a direct key lookup. — Replace with `if Customer.Get(CustomerNo) then exit(Customer.Name);` which is a direct PK lookup (CodeCop AA0233).", "severity": "medium", "domain": "performance"}, {"file": "src/CustomerLookup.Codeunit.al", "line_start": 18, "line_end": 18, "body": "Count() > 0 on Customer (up to 800k rows) for a pure existence check. Count() materializes a SQL COUNT(*) over the filtered set instead of stopping at the first matching row. — Replace with `exit(not Customer.IsEmpty());` which stops at the first match and is significantly cheaper on large tables.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "False positive performance findings: findset_false_positive (69 false positives). Agent flagged these but reviewers rejected them. Enriched with 2 true-positive findings in addition to the false-positive bait.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-003", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/MigrationSetupHandler.Codeunit.al b/src/MigrationSetupHandler.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/MigrationSetupHandler.Codeunit.al\n@@ -0,0 +1,18 @@\n+codeunit 50221 \"Migration Setup Handler\"\n+{\n+ procedure CountMigratablePermissionSets(): Integer\n+ var\n+ PermissionSet: Record \"Permission Set\";\n+ begin\n+ PermissionSet.SetFilter(\"Role ID\", '%1|%2', 'D365 BASIC', 'D365 READ');\n+ exit(PermissionSet.Count());\n+ end;\n+\n+ procedure CountObsoleteRegisters(): Integer\n+ var\n+ DateComprRegister: Record \"Date Compr. Register\";\n+ begin\n+ DateComprRegister.SetFilter(\"Ending Date\", '<%1', CalcDate('<-2Y>', Today));\n+ exit(DateComprRegister.Count());\n+ end;\n+}\ndiff --git a/src/PermissionSetListOverview.Page.al b/src/PermissionSetListOverview.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PermissionSetListOverview.Page.al\n@@ -0,0 +1,84 @@\n+page 50220 \"Permission Set List Overview\"\n+{\n+ PageType = List;\n+ ApplicationArea = All;\n+ UsageCategory = Administration;\n+ SourceTable = \"Aggregate Permission Set\";\n+ Caption = 'Permission Set List Overview';\n+ Editable = false;\n+\n+ layout\n+ {\n+ area(Content)\n+ {\n+ repeater(Permissions)\n+ {\n+ field(\"Role ID\"; Rec.\"Role ID\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the identifier of the permission set.';\n+ }\n+\n+ field(Name; Rec.Name)\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the display name of the permission set.';\n+ }\n+\n+ field(Scope; Rec.Scope)\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies whether the permission set is defined by the system or by a tenant.';\n+ }\n+\n+ field(\"App Name\"; Rec.\"App Name\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the name of the extension that defines the permission set.';\n+ }\n+\n+ field(\"Permission Count\"; PermissionCount)\n+ {\n+ Caption = 'Permission Count';\n+ ApplicationArea = All;\n+ Editable = false;\n+ ToolTip = 'Specifies the number of permissions that belong to the permission set.';\n+ }\n+ }\n+ }\n+ }\n+\n+ actions\n+ {\n+ area(Processing)\n+ {\n+ action(RefreshCounts)\n+ {\n+ Caption = 'Refresh Permission Counts';\n+ ApplicationArea = All;\n+ ToolTip = 'Recalculates the permission count shown for each permission set.';\n+\n+ trigger OnAction()\n+ begin\n+ CurrPage.Update();\n+ end;\n+ }\n+ }\n+ }\n+\n+ var\n+ PermissionCount: Integer;\n+\n+ trigger OnAfterGetRecord()\n+ var\n+ Permission: Record Permission;\n+ begin\n+ Permission.SetRange(\"Role ID\", Rec.\"Role ID\");\n+ PermissionCount := Permission.Count();\n+ end;\n+\n+ trigger OnOpenPage()\n+ begin\n+ Rec.SetFilter(Scope, '%1|%2', Rec.Scope::System, Rec.Scope::Tenant);\n+ end;\n+}\ndiff --git a/src/SalesInvoiceFilter.Codeunit.al b/src/SalesInvoiceFilter.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SalesInvoiceFilter.Codeunit.al\n@@ -0,0 +1,26 @@\n+codeunit 50222 \"Sales Invoice Filter\"\n+{\n+ procedure ListLinesByDescription(Description: Text[100])\n+ var\n+ SalesInvoiceLine: Record \"Sales Invoice Line\";\n+ begin\n+ SalesInvoiceLine.SetRange(Description, Description);\n+ if SalesInvoiceLine.FindSet() then\n+ repeat\n+ Message('%1 %2', SalesInvoiceLine.\"Document No.\", SalesInvoiceLine.\"Line No.\");\n+ until SalesInvoiceLine.Next() = 0;\n+ end;\n+\n+ procedure SumQuantityByDocument(DocumentNo: Code[20]): Decimal\n+ var\n+ SalesInvoiceLine: Record \"Sales Invoice Line\";\n+ Total: Decimal;\n+ begin\n+ SalesInvoiceLine.SetRange(\"Document No.\", DocumentNo);\n+ if SalesInvoiceLine.FindSet() then\n+ repeat\n+ Total += SalesInvoiceLine.Quantity;\n+ until SalesInvoiceLine.Next() = 0;\n+ exit(Total);\n+ end;\n+}\n", "expected_comments": [{"file": "src/SalesInvoiceFilter.Codeunit.al", "line_start": 7, "line_end": 7, "body": "SetRange on Sales Invoice Line.Description with no SetCurrentKey and no key including Description. Sales Invoice Line is large (up to 3M rows) and the query will table-scan. — Either add `SetCurrentKey` to a key whose leading field matches the filter, or introduce a new key on the source table that covers Description, before filtering.", "severity": "high", "domain": "performance"}, {"file": "src/SalesInvoiceFilter.Codeunit.al", "line_start": 20, "line_end": 20, "body": "FindSet over Sales Invoice Line (3M rows, ~80 fields) loads every field for every row but the loop only reads `Quantity`. — Add `SalesInvoiceLine.SetLoadFields(Quantity);` before SetRange so SQL returns only the Quantity column (plus key fields). Even better, replace the loop with `SalesInvoiceLine.CalcSums(Quantity)` since Quantity is a SumIndexField on this table.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "False positive performance findings: index_false_positive (29 false positives). Agent flagged these but reviewers rejected them. Enriched with 2 true-positive findings in addition to the false-positive bait.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-004", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/BatchModifier.Codeunit.al b/src/BatchModifier.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/BatchModifier.Codeunit.al\n@@ -0,0 +1,22 @@\n+codeunit 50231 \"Batch Modifier FP\"\n+{\n+ procedure UpdateSetupDefaults(CompanyCode: Code[10])\n+ var\n+ FASetup: Record \"FA Setup\";\n+ begin\n+ // CORRECT: FA Setup is a singleton — direct Get + Modify is ideal\n+ if FASetup.Get() then begin\n+ FASetup.\"Default Depr. Book\" := CompanyCode;\n+ FASetup.Modify(false);\n+ end;\n+ end;\n+\n+ procedure CleanupObsoleteReasonCodes()\n+ var\n+ ReasonCode: Record \"Reason Code\";\n+ begin\n+ // CORRECT: DeleteAll is the proper bulk delete pattern\n+ ReasonCode.SetRange(Description, '');\n+ ReasonCode.DeleteAll(false);\n+ end;\n+}\ndiff --git a/src/CustomerReader.Codeunit.al b/src/CustomerReader.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerReader.Codeunit.al\n@@ -0,0 +1,12 @@\n+codeunit 50232 \"Customer Reader\"\n+{\n+ procedure GetCustomerCreditLimit(CustomerNo: Code[20]): Decimal\n+ var\n+ Customer: Record Customer;\n+ begin\n+ Customer.LockTable();\n+ if Customer.Get(CustomerNo) then\n+ exit(Customer.\"Credit Limit (LCY)\");\n+ exit(0);\n+ end;\n+}\n", "expected_comments": [{"file": "src/CustomerReader.Codeunit.al", "line_start": 7, "line_end": 7, "body": "LockTable() in a read-only helper that only Gets a Customer and returns a field. This acquires UPDLOCK for the rest of the transaction on every caller, causing unnecessary lock contention. — Remove LockTable() (Customer.Get is fine without it), or use `Customer.ReadIsolation := IsolationLevel::ReadCommitted` if a specific read isolation is desired.", "severity": "high", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "False positive performance findings: locking_fp. Correct Get+Modify on singleton setup table and DeleteAll for bulk delete. Enriched with one true-positive finding in addition to the false-positive bait.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-005", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/EnumIterator.Codeunit.al b/src/EnumIterator.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/EnumIterator.Codeunit.al\n@@ -0,0 +1,44 @@\n+codeunit 50240 \"Enum Iterator\"\n+{\n+ procedure CountMatchingStatusLines(var TempWorksheet: Record \"Integer\" temporary): Integer\n+ var\n+ AnalysisReportName: Record \"Analysis Report Name\";\n+ MatchCount: Integer;\n+ begin\n+ if TempWorksheet.FindSet() then\n+ repeat\n+ AnalysisReportName.SetRange(\"Analysis Area\", AnalysisReportName.\"Analysis Area\"::Sales);\n+ if AnalysisReportName.Get(Format(TempWorksheet.Number)) then\n+ MatchCount += 1;\n+ until TempWorksheet.Next() = 0;\n+ exit(MatchCount);\n+ end;\n+\n+ procedure CountConfiguredLines(var TempBuffer: Record \"Name/Value Buffer\" temporary): Integer\n+ var\n+ ConfigPackageTable: Record \"Config. Package Table\";\n+ ConfiguredCount: Integer;\n+ begin\n+ if TempBuffer.FindSet() then\n+ repeat\n+ ConfigPackageTable.SetRange(\"Table Name\", TempBuffer.Name);\n+ if not ConfigPackageTable.IsEmpty() then\n+ ConfiguredCount += 1;\n+ until TempBuffer.Next() = 0;\n+ exit(ConfiguredCount);\n+ end;\n+\n+ procedure CountCountriesWithCurrency(var TempCountryRegion: Record \"Country/Region\" temporary): Integer\n+ var\n+ Currency: Record Currency;\n+ WithCurrencyCount: Integer;\n+ begin\n+ if TempCountryRegion.FindSet() then\n+ repeat\n+ Currency.SetRange(\"ISO Code\", TempCountryRegion.\"ISO Code\");\n+ if not Currency.IsEmpty() then\n+ WithCurrencyCount += 1;\n+ until TempCountryRegion.Next() = 0;\n+ exit(WithCurrencyCount);\n+ end;\n+}\ndiff --git a/src/RoleProcessor.Codeunit.al b/src/RoleProcessor.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/RoleProcessor.Codeunit.al\n@@ -0,0 +1,27 @@\n+codeunit 50241 \"Role Processor\"\n+{\n+ procedure CountUsersWithRole(RoleId: Code[20]): Integer\n+ var\n+ AccessControl: Record \"Access Control\";\n+ begin\n+ AccessControl.SetRange(\"Role ID\", RoleId);\n+ exit(AccessControl.Count());\n+ end;\n+\n+ procedure CountDepartmentUsers(DepartmentCode: Code[20]): Integer\n+ var\n+ UserSetup: Record \"User Setup\";\n+ begin\n+ UserSetup.SetRange(\"Global Dimension 1 Code\", DepartmentCode);\n+ exit(UserSetup.Count());\n+ end;\n+\n+ procedure CountEnabledFullUsers(): Integer\n+ var\n+ User: Record User;\n+ begin\n+ User.SetRange(\"License Type\", User.\"License Type\"::\"Full User\");\n+ User.SetRange(State, User.State::Enabled);\n+ exit(User.Count());\n+ end;\n+}\ndiff --git a/src/ItemLedgerSummary.Codeunit.al b/src/ItemLedgerSummary.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ItemLedgerSummary.Codeunit.al\n@@ -0,0 +1,20 @@\n+codeunit 50242 \"Item Ledger Summary\"\n+{\n+ procedure BuildItemLedgerReport(ItemNo: Code[20]): Text\n+ var\n+ ItemLedgerEntry: Record \"Item Ledger Entry\";\n+ RecRef: RecordRef;\n+ FldRef: FieldRef;\n+ Result: Text;\n+ begin\n+ ItemLedgerEntry.SetRange(\"Item No.\", ItemNo);\n+ RecRef.GetTable(ItemLedgerEntry);\n+ if RecRef.FindSet() then\n+ repeat\n+ FldRef := RecRef.Field(ItemLedgerEntry.FieldNo(Quantity));\n+ Result += Format(FldRef.Value) + ';';\n+ Commit();\n+ until RecRef.Next() = 0;\n+ exit(Result);\n+ end;\n+}\n", "expected_comments": [{"file": "src/ItemLedgerSummary.Codeunit.al", "line_start": 14, "line_end": 14, "body": "RecordRef/FieldRef used inside a FindSet loop over Item Ledger Entry (up to 10M rows) when a typed Record is in scope. RecordRef/FieldRef are slower than direct typed access and offer no benefit here. — Iterate `ItemLedgerEntry` directly with `Format(ItemLedgerEntry.Quantity)`, or better, replace the loop with `ItemLedgerEntry.CalcSums(Quantity)` (Quantity is a SumIndexField on Item Ledger Entry).", "severity": "high", "domain": "performance"}, {"file": "src/ItemLedgerSummary.Codeunit.al", "line_start": 15, "line_end": 15, "body": "String concatenation with `Result += ...` inside a repeat..until loop allocates and copies the entire accumulated string on every iteration — O(n^2) memory and CPU for large record sets. — Use `TextBuilder` (declare `Result: TextBuilder;`, call `Result.Append(...)`, and `exit(Result.ToText())`) which appends in amortized O(1).", "severity": "medium", "domain": "performance"}, {"file": "src/ItemLedgerSummary.Codeunit.al", "line_start": 16, "line_end": 16, "body": "Commit() inside the repeat..until creates one transaction boundary per row. On Item Ledger Entry (up to 10M rows) this multiplies SQL transaction overhead and prevents the engine from batching writes. — Remove the in-loop Commit; if a single commit is needed after the iteration, place it after the `until ... Next() = 0;` line.", "severity": "high", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "False positive performance findings: loop_false_positive (81 false positives). Agent flagged these but reviewers rejected them. Enriched with 3 true-positive findings in addition to the false-positive bait.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-006", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/GenericFieldCopier.Codeunit.al b/src/GenericFieldCopier.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/GenericFieldCopier.Codeunit.al\n@@ -0,0 +1,34 @@\n+codeunit 50250 \"Generic Field Copier\"\n+{\n+ procedure CopyFieldsByRef(SourceRecRef: RecordRef; var DestRecRef: RecordRef)\n+ var\n+ SourceFldRef: FieldRef;\n+ DestFldRef: FieldRef;\n+ i: Integer;\n+ begin\n+ for i := 1 to SourceRecRef.FieldCount() do begin\n+ SourceFldRef := SourceRecRef.FieldIndex(i);\n+ if SourceFldRef.Class = FieldClass::Normal then\n+ if DestRecRef.FieldExist(SourceFldRef.Number) then begin\n+ DestFldRef := DestRecRef.Field(SourceFldRef.Number);\n+ if DestFldRef.Type = SourceFldRef.Type then\n+ DestFldRef.Value := SourceFldRef.Value;\n+ end;\n+ end;\n+ end;\n+\n+ procedure CountCopyableFields(SourceRecRef: RecordRef; DestRecRef: RecordRef): Integer\n+ var\n+ SourceFldRef: FieldRef;\n+ i: Integer;\n+ CopyableCount: Integer;\n+ begin\n+ for i := 1 to SourceRecRef.FieldCount() do begin\n+ SourceFldRef := SourceRecRef.FieldIndex(i);\n+ if SourceFldRef.Class = FieldClass::Normal then\n+ if DestRecRef.FieldExist(SourceFldRef.Number) then\n+ CopyableCount += 1;\n+ end;\n+ exit(CopyableCount);\n+ end;\n+}\ndiff --git a/src/MetadataReader.Codeunit.al b/src/MetadataReader.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/MetadataReader.Codeunit.al\n@@ -0,0 +1,36 @@\n+codeunit 50251 \"Metadata Reader\"\n+{\n+ procedure GetTableNames(): List of [Text]\n+ var\n+ TableMetadata: Record \"Table Metadata\";\n+ Names: List of [Text];\n+ begin\n+ TableMetadata.SetRange(TableType, TableMetadata.TableType::Normal);\n+ if TableMetadata.FindSet() then\n+ repeat\n+ Names.Add(TableMetadata.Name);\n+ until TableMetadata.Next() = 0;\n+ exit(Names);\n+ end;\n+\n+ procedure GetFieldNames(TableNo: Integer): List of [Text]\n+ var\n+ FieldMetadata: Record Field;\n+ FieldNames: List of [Text];\n+ begin\n+ FieldMetadata.SetRange(TableNo, TableNo);\n+ FieldMetadata.SetRange(Class, FieldMetadata.Class::Normal);\n+ if FieldMetadata.FindSet() then\n+ repeat\n+ FieldNames.Add(FieldMetadata.FieldName);\n+ until FieldMetadata.Next() = 0;\n+ exit(FieldNames);\n+ end;\n+\n+ procedure CheckTableExists(TableNo: Integer): Boolean\n+ var\n+ TableMetadata: Record \"Table Metadata\";\n+ begin\n+ exit(TableMetadata.Get(TableNo));\n+ end;\n+}\ndiff --git a/src/SalesLineQuantitySubscriber.Codeunit.al b/src/SalesLineQuantitySubscriber.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SalesLineQuantitySubscriber.Codeunit.al\n@@ -0,0 +1,27 @@\n+codeunit 50252 \"Sales Line Quantity Subscriber\"\n+{\n+ [EventSubscriber(ObjectType::Table, Database::\"Sales Line\", 'OnAfterValidateEvent', 'Quantity', false, false)]\n+ local procedure OnAfterValidateQuantity(var Rec: Record \"Sales Line\")\n+ var\n+ Item: Record Item;\n+ begin\n+ Item.Get(Rec.\"No.\");\n+ if Item.\"Inventory Posting Group\" <> '' then\n+ UpdatePostingGroup(Rec, Item);\n+ end;\n+\n+ [EventSubscriber(ObjectType::Table, Database::\"Sales Line\", 'OnAfterValidateEvent', 'Unit Price', false, false)]\n+ local procedure OnAfterValidateUnitPrice(var Rec: Record \"Sales Line\")\n+ var\n+ Item: Record Item;\n+ begin\n+ Item.Get(Rec.\"No.\");\n+ if Item.\"Price Includes VAT\" then\n+ Rec.\"Unit Price Excl. VAT\" := Rec.\"Unit Price\" / (1 + Rec.\"VAT %\" / 100);\n+ end;\n+\n+ local procedure UpdatePostingGroup(var SalesLine: Record \"Sales Line\"; Item: Record Item)\n+ begin\n+ SalesLine.\"Posting Group\" := Item.\"Inventory Posting Group\";\n+ end;\n+}\n", "expected_comments": [{"file": "src/SalesLineQuantitySubscriber.Codeunit.al", "line_start": 8, "line_end": 8, "body": "Item.Get(Rec.\"No.\") fires on every Quantity validation in Sales Line with no cheap guard. The subscriber runs even for Type::\"G/L Account\", Type::Resource, etc., issuing a DB lookup against Item (800k rows) that is then discarded. — Guard with the cheap typed check first: `if Rec.Type <> Rec.Type::Item then exit;` before Item.Get, and consider `Item.SetLoadFields(\"Inventory Posting Group\")` since only one field is read.", "severity": "high", "domain": "performance"}, {"file": "src/SalesLineQuantitySubscriber.Codeunit.al", "line_start": 18, "line_end": 18, "body": "Same N+1/no-guard pattern in the Unit Price subscriber: Item.Get on every Unit Price validation, regardless of Sales Line Type. Unit Price validation is one of the most frequently fired events on Sales Line, multiplying the cost. — Guard with `if Rec.Type <> Rec.Type::Item then exit;` first, and use `Item.SetLoadFields(\"Price Includes VAT\")` since only one field is read.", "severity": "high", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "False positive performance findings: other_performance (86 false positives). Agent flagged these but reviewers rejected them. Enriched with 2 true-positive findings in addition to the false-positive bait.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-007", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/TransactionProcessor.Codeunit.al b/src/TransactionProcessor.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/TransactionProcessor.Codeunit.al\n@@ -0,0 +1,21 @@\n+codeunit 50403 \"Transaction Processor\"\n+{\n+ procedure GetPaymentDiscount(TermsCode: Code[10]): Decimal\n+ var\n+ PaymentTerms: Record \"Payment Terms\";\n+ begin\n+ if PaymentTerms.Get(TermsCode) then\n+ exit(PaymentTerms.\"Discount %\");\n+ exit(0);\n+ end;\n+\n+ procedure GetReasonCodeDescription(ReasonCode: Code[10]): Text[100]\n+ var\n+ ReasonCodeRec: Record \"Reason Code\";\n+ begin\n+ ReasonCodeRec.ReadIsolation := IsolationLevel::ReadCommitted;\n+ if ReasonCodeRec.Get(ReasonCode) then\n+ exit(ReasonCodeRec.Description);\n+ exit('');\n+ end;\n+}\ndiff --git a/src/CustomerReportReader.Codeunit.al b/src/CustomerReportReader.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerReportReader.Codeunit.al\n@@ -0,0 +1,16 @@\n+codeunit 50404 \"Customer Report Reader\"\n+{\n+ procedure ExportActiveCustomerNumbers(): Text\n+ var\n+ Customer: Record Customer;\n+ Result: TextBuilder;\n+ begin\n+ Customer.SetRange(Blocked, Customer.Blocked::\" \");\n+ if Customer.FindSet(true) then\n+ repeat\n+ Result.Append(Customer.\"No.\");\n+ Result.Append(';');\n+ until Customer.Next() = 0;\n+ exit(Result.ToText());\n+ end;\n+}\n", "expected_comments": [{"file": "src/CustomerReportReader.Codeunit.al", "line_start": 9, "line_end": 9, "body": "FindSet(true) requests UpdLock on Customer rows for an iteration that only reads (\"No.\") and never modifies the records. This blocks other transactions reading or writing Customer for no benefit. — Use `Customer.FindSet()` (or `FindSet(false)`) for read-only iterations; reserve FindSet(true) for loops that actually call Modify.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "False positive performance findings: readisolation_fp. All LockTable and ReadIsolation patterns are correctly used for their respective write scenarios. Enriched with one true-positive finding in addition to the false-positive bait.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-008", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/CopyLocationHandler.Codeunit.al b/src/CopyLocationHandler.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CopyLocationHandler.Codeunit.al\n@@ -0,0 +1,68 @@\n+codeunit 50261 \"Copy Location Handler FP\"\n+{\n+ procedure CopyLocation(SourceCode: Code[10]; TargetCode: Code[10])\n+ var\n+ SourceLocation: Record Location;\n+ TargetLocation: Record Location;\n+ begin\n+ // CORRECT: SetLoadFields before Get — only loads the fields we actually copy\n+ SourceLocation.SetLoadFields(Name, Address, \"Address 2\", City, \"Post Code\", \"Country/Region Code\", \"Phone No.\", Contact);\n+ if not SourceLocation.Get(SourceCode) then\n+ exit;\n+\n+ TargetLocation.Init();\n+ TargetLocation.Code := TargetCode;\n+ TargetLocation.Name := SourceLocation.Name;\n+ TargetLocation.Address := SourceLocation.Address;\n+ TargetLocation.\"Address 2\" := SourceLocation.\"Address 2\";\n+ TargetLocation.City := SourceLocation.City;\n+ TargetLocation.\"Post Code\" := SourceLocation.\"Post Code\";\n+ TargetLocation.\"Country/Region Code\" := SourceLocation.\"Country/Region Code\";\n+ TargetLocation.\"Phone No.\" := SourceLocation.\"Phone No.\";\n+ TargetLocation.Contact := SourceLocation.Contact;\n+ TargetLocation.Insert(true);\n+ end;\n+\n+ procedure CopyCustomer(SourceNo: Code[20]; TargetNo: Code[20])\n+ var\n+ SourceCustomer: Record Customer;\n+ TargetCustomer: Record Customer;\n+ begin\n+ // CORRECT: SetLoadFields on Customer (800k rows, 100+ fields) — only loads needed subset\n+ SourceCustomer.SetLoadFields(Name, \"Name 2\", Address, \"Address 2\", City, \"Post Code\",\n+ \"Country/Region Code\", \"Phone No.\", \"Customer Posting Group\",\n+ \"Gen. Bus. Posting Group\", \"Payment Terms Code\", \"Payment Method Code\");\n+ if not SourceCustomer.Get(SourceNo) then\n+ exit;\n+\n+ TargetCustomer.Init();\n+ TargetCustomer.\"No.\" := TargetNo;\n+ TargetCustomer.Name := SourceCustomer.Name;\n+ TargetCustomer.\"Name 2\" := SourceCustomer.\"Name 2\";\n+ TargetCustomer.Address := SourceCustomer.Address;\n+ TargetCustomer.\"Address 2\" := SourceCustomer.\"Address 2\";\n+ TargetCustomer.City := SourceCustomer.City;\n+ TargetCustomer.\"Post Code\" := SourceCustomer.\"Post Code\";\n+ TargetCustomer.\"Country/Region Code\" := SourceCustomer.\"Country/Region Code\";\n+ TargetCustomer.\"Phone No.\" := SourceCustomer.\"Phone No.\";\n+ TargetCustomer.\"Customer Posting Group\" := SourceCustomer.\"Customer Posting Group\";\n+ TargetCustomer.\"Gen. Bus. Posting Group\" := SourceCustomer.\"Gen. Bus. Posting Group\";\n+ TargetCustomer.\"Payment Terms Code\" := SourceCustomer.\"Payment Terms Code\";\n+ TargetCustomer.\"Payment Method Code\" := SourceCustomer.\"Payment Method Code\";\n+ TargetCustomer.Insert(true);\n+ end;\n+\n+ procedure BackupLocationData(LocationCode: Code[10]): Text\n+ var\n+ Location: Record Location;\n+ begin\n+ // CORRECT: SetLoadFields — only 5 of ~40 fields needed for backup\n+ Location.SetLoadFields(Name, Address, City, \"Country/Region Code\");\n+ if not Location.Get(LocationCode) then\n+ exit('');\n+\n+ exit(StrSubstNo('%1|%2|%3|%4|%5',\n+ Location.Code, Location.Name, Location.Address,\n+ Location.City, Location.\"Country/Region Code\"));\n+ end;\n+}\ndiff --git a/src/SetupValidator.Codeunit.al b/src/SetupValidator.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SetupValidator.Codeunit.al\n@@ -0,0 +1,102 @@\n+codeunit 50260 \"Setup Validator FP\"\n+{\n+ procedure ValidateSetup()\n+ var\n+ FASetup: Record \"FA Setup\";\n+ GLSetup: Record \"General Ledger Setup\";\n+ SalesSetup: Record \"Sales & Receivables Setup\";\n+ PurchSetup: Record \"Purchases & Payables Setup\";\n+ InventorySetup: Record \"Inventory Setup\";\n+ begin\n+ // CORRECT: Setup tables are singletons (1 record per company)\n+ // Get() on singleton tables is always appropriate and fast\n+\n+ FASetup.Get();\n+ FASetup.TestField(\"Default Depr. Book\");\n+\n+ GLSetup.Get();\n+ GLSetup.TestField(\"LCY Code\");\n+ GLSetup.TestField(\"Posting Allowed From\");\n+ GLSetup.TestField(\"Posting Allowed To\");\n+\n+ SalesSetup.Get();\n+ SalesSetup.TestField(\"Customer Nos.\");\n+\n+ PurchSetup.Get();\n+ PurchSetup.TestField(\"Vendor Nos.\");\n+\n+ InventorySetup.Get();\n+ InventorySetup.TestField(\"Item Nos.\");\n+\n+ // Validate cross-setup consistency\n+ ValidateCurrencyConsistency(GLSetup, SalesSetup);\n+ end;\n+\n+ procedure GetLocationName(LocationCode: Code[10]): Text\n+ var\n+ Location: Record Location;\n+ begin\n+ // CORRECT: SetLoadFields + Get is the Microsoft-recommended pattern\n+ // This loads only the required fields, making it more efficient than full Get\n+ Location.SetLoadFields(Name);\n+ if Location.Get(LocationCode) then\n+ exit(Location.Name);\n+ exit('');\n+ end;\n+\n+ procedure GetCustomerInfo(CustomerNo: Code[20]; var Name: Text; var CreditLimit: Decimal)\n+ var\n+ Customer: Record Customer;\n+ begin\n+ // CORRECT: SetLoadFields pattern for loading specific fields only\n+ Customer.SetLoadFields(Name, \"Credit Limit (LCY)\");\n+ if Customer.Get(CustomerNo) then begin\n+ Name := Customer.Name;\n+ CreditLimit := Customer.\"Credit Limit (LCY)\";\n+ end else begin\n+ Name := '';\n+ CreditLimit := 0;\n+ end;\n+ end;\n+\n+ procedure GetVendorPaymentTerms(VendorNo: Code[20]): Code[10]\n+ var\n+ Vendor: Record Vendor;\n+ begin\n+ // CORRECT: Loading only the specific field needed\n+ Vendor.SetLoadFields(\"Payment Terms Code\");\n+ if Vendor.Get(VendorNo) then\n+ exit(Vendor.\"Payment Terms Code\");\n+ exit('');\n+ end;\n+\n+ local procedure ValidateCurrencyConsistency(GLSetup: Record \"General Ledger Setup\"; SalesSetup: Record \"Sales & Receivables Setup\")\n+ var\n+ Currency: Record Currency;\n+ begin\n+ // CORRECT: Single Get() call for validation\n+ if GLSetup.\"Additional Reporting Currency\" <> '' then begin\n+ Currency.Get(GLSetup.\"Additional Reporting Currency\");\n+ Currency.TestField(\"Amount Rounding Precision\");\n+ end;\n+ end;\n+\n+ procedure ValidateNumberSeries()\n+ var\n+ SalesSetup: Record \"Sales & Receivables Setup\";\n+ NoSeries: Record \"No. Series\";\n+ begin\n+ // CORRECT: Setup validation with related record checks\n+ SalesSetup.Get();\n+\n+ if SalesSetup.\"Customer Nos.\" <> '' then begin\n+ NoSeries.Get(SalesSetup.\"Customer Nos.\");\n+ NoSeries.TestField(\"Default Nos.\", true);\n+ end;\n+\n+ if SalesSetup.\"Invoice Nos.\" <> '' then begin\n+ NoSeries.Get(SalesSetup.\"Invoice Nos.\");\n+ NoSeries.TestField(\"Default Nos.\", true);\n+ end;\n+ end;\n+}\ndiff --git a/src/CustomerNameBuilder.Codeunit.al b/src/CustomerNameBuilder.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerNameBuilder.Codeunit.al\n@@ -0,0 +1,38 @@\n+codeunit 50262 \"Customer Name Builder\"\n+{\n+ procedure BuildCustomerNameList(CountryRegionCode: Code[10]): Text\n+ var\n+ Customer: Record Customer;\n+ Result: TextBuilder;\n+ begin\n+ Customer.SetRange(\"Country/Region Code\", CountryRegionCode);\n+ if Customer.FindSet() then\n+ repeat\n+ Result.Append(Customer.Name);\n+ Result.Append(';');\n+ until Customer.Next() = 0;\n+ exit(Result.ToText());\n+ end;\n+\n+ procedure BuildItemDescriptionList(InventoryPostingGroup: Code[20]): Text\n+ var\n+ Item: Record Item;\n+ Result: TextBuilder;\n+ begin\n+ Item.SetRange(\"Inventory Posting Group\", InventoryPostingGroup);\n+ if Item.FindSet() then\n+ repeat\n+ Result.Append(Item.Description);\n+ Result.Append(';');\n+ until Item.Next() = 0;\n+ exit(Result.ToText());\n+ end;\n+\n+ procedure GetCustomerNameFromLedgerEntry(CustLedgerEntry: Record \"Cust. Ledger Entry\"): Text[100]\n+ var\n+ Customer: Record Customer;\n+ begin\n+ Customer.Get(CustLedgerEntry.\"Customer No.\");\n+ exit(Customer.Name);\n+ end;\n+}\n", "expected_comments": [{"file": "src/CustomerNameBuilder.Codeunit.al", "line_start": 9, "line_end": 9, "body": "FindSet over Customer (800k rows, 100+ fields) loads every field for every row but the loop only reads `Customer.Name`. — Add `Customer.SetLoadFields(Name);` before SetRange so SQL returns only the Name column (plus key fields) — the gain scales with the row count and is significant for hot tables like Customer.", "severity": "high", "domain": "performance"}, {"file": "src/CustomerNameBuilder.Codeunit.al", "line_start": 23, "line_end": 23, "body": "Same anti-pattern on Item (800k rows, ~80 fields): FindSet loads every field but the loop only reads `Item.Description`. — Add `Item.SetLoadFields(Description);` before SetRange to limit the SQL projection to a single column.", "severity": "high", "domain": "performance"}, {"file": "src/CustomerNameBuilder.Codeunit.al", "line_start": 35, "line_end": 35, "body": "Customer.Get on a wide table (800k rows, 100+ fields) when only `Name` is returned. The full record is loaded just to read one field. — Add `Customer.SetLoadFields(Name);` before the Get to load only the field that's actually used.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "False positive performance findings: record_loading_fp (28 false positives). Agent flagged these but reviewers rejected them. Enriched with 3 true-positive findings in addition to the false-positive bait.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-009", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/NameValueBufferAPI.Page.al b/src/NameValueBufferAPI.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/NameValueBufferAPI.Page.al\n@@ -0,0 +1,63 @@\n+page 50271 \"Name Value Buffer API\"\n+{\n+ PageType = API;\n+ APIGroup = 'configuration';\n+ APIVersion = 'v1.0';\n+ EntityName = 'nameValueBuffer';\n+ EntitySetName = 'nameValueBuffers';\n+ SourceTable = \"Name/Value Buffer\";\n+ SourceTableTemporary = true;\n+ DelayedInsert = true;\n+\n+ layout\n+ {\n+ area(Content)\n+ {\n+ repeater(Buffers)\n+ {\n+ field(id; Rec.ID)\n+ {\n+ Caption = 'ID';\n+ }\n+\n+ field(name; Rec.Name)\n+ {\n+ Caption = 'Name';\n+ }\n+\n+ field(value; Rec.Value)\n+ {\n+ Caption = 'Value';\n+ }\n+\n+ field(valueLong; Rec.\"Value BLOB\")\n+ {\n+ Caption = 'Value Long';\n+ }\n+ }\n+ }\n+ }\n+\n+ trigger OnInsertRecord(BelowxRec: Boolean): Boolean\n+ var\n+ NextID: Integer;\n+ begin\n+ if Rec.FindLast() then\n+ NextID := Rec.ID + 1\n+ else\n+ NextID := 1;\n+\n+ Rec.ID := NextID;\n+ exit(true);\n+ end;\n+\n+ trigger OnModifyRecord(): Boolean\n+ begin\n+ if Rec.Name = '' then\n+ Error(NameEmptyErr);\n+ exit(true);\n+ end;\n+\n+ var\n+ NameEmptyErr: Label 'Name cannot be empty';\n+}\ndiff --git a/src/RecordSetAggregator.Codeunit.al b/src/RecordSetAggregator.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/RecordSetAggregator.Codeunit.al\n@@ -0,0 +1,33 @@\n+codeunit 50270 \"Record Set Aggregator\"\n+{\n+ Access = Internal;\n+\n+ procedure CalculateOutstandingTotal(var TempSalesLine: Record \"Sales Line\" temporary): Decimal\n+ var\n+ Total: Decimal;\n+ begin\n+ if TempSalesLine.FindSet() then\n+ repeat\n+ TempSalesLine.CalcFields(\"Outstanding Amount\");\n+ Total += TempSalesLine.\"Outstanding Amount\";\n+ until TempSalesLine.Next() = 0;\n+ exit(Total);\n+ end;\n+\n+ procedure FindMaxUnitPrice(var TempItem: Record Item temporary): Decimal\n+ var\n+ MaxUnitPrice: Decimal;\n+ begin\n+ if TempItem.FindSet() then\n+ repeat\n+ if TempItem.\"Unit Price\" > MaxUnitPrice then\n+ MaxUnitPrice := TempItem.\"Unit Price\";\n+ until TempItem.Next() = 0;\n+ exit(MaxUnitPrice);\n+ end;\n+\n+ procedure CountAnalysisEntries(var TempAnalysisReportChartSetup: Record \"Analysis Report Chart Setup\" temporary): Integer\n+ begin\n+ exit(TempAnalysisReportChartSetup.Count());\n+ end;\n+}\ndiff --git a/src/OutboxEmailAPI.Page.al b/src/OutboxEmailAPI.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/OutboxEmailAPI.Page.al\n@@ -0,0 +1,34 @@\n+page 50272 \"Outbox Email API\"\n+{\n+ PageType = API;\n+ APIGroup = 'email';\n+ APIVersion = 'v1.0';\n+ EntityName = 'outboxEmail';\n+ EntitySetName = 'outboxEmails';\n+ SourceTable = \"Email Outbox\";\n+ DelayedInsert = true;\n+\n+ layout\n+ {\n+ area(Content)\n+ {\n+ repeater(Outbox)\n+ {\n+ field(id; Rec.Id)\n+ {\n+ Caption = 'Id';\n+ }\n+\n+ field(description; Rec.Description)\n+ {\n+ Caption = 'Description';\n+ }\n+\n+ field(status; Rec.Status)\n+ {\n+ Caption = 'Status';\n+ }\n+ }\n+ }\n+ }\n+}\n", "expected_comments": [{"file": "src/OutboxEmailAPI.Page.al", "line_start": 8, "line_end": 8, "body": "API page on \"Email Outbox\" declares SourceTable without `SourceTableTemporary = true`. API pages are hit by external callers at high frequency; backing them with a persistent table puts unnecessary load on the database and on the underlying table (which is meant to be staged in-memory for API responses). — Add `SourceTableTemporary = true;` so the page operates in-memory like the sibling Name/Value Buffer API page in this change.", "severity": "high", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "False positive performance findings: temp_table_fp (3 false positives). Agent flagged these but reviewers rejected them. Enriched with one true-positive finding in addition to the false-positive bait.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-010", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/PaymentToleranceMgt.Codeunit.al b/src/PaymentToleranceMgt.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PaymentToleranceMgt.Codeunit.al\n@@ -0,0 +1,29 @@\n+codeunit 50101 \"Payment Tolerance Mgt.\"\n+{\n+ Access = Internal;\n+\n+ procedure ApplyTolerances(DocumentNo: Code[20])\n+ var\n+ SalesLine: Record \"Sales Line\";\n+ NewPrice: Decimal;\n+ begin\n+ if DocumentNo = '' then\n+ exit;\n+\n+ NewPrice := GetDefaultUnitPrice();\n+\n+ SalesLine.SetRange(\"Document No.\", DocumentNo);\n+ SalesLine.SetRange(Type, SalesLine.Type::Item);\n+\n+ if SalesLine.FindSet() then\n+ repeat\n+ SalesLine.Validate(\"Unit Price\", NewPrice);\n+ SalesLine.Modify(true);\n+ until SalesLine.Next() = 0;\n+ end;\n+\n+ local procedure GetDefaultUnitPrice(): Decimal\n+ begin\n+ exit(10.00);\n+ end;\n+}\n", "expected_comments": [{"file": "src/PaymentToleranceMgt.Codeunit.al", "line_start": 19, "line_end": 19, "body": "Loop + Validate + Modify(true) on Sales Line to update a single field ('Unit Price'). ModifyAll would execute as a single SQL UPDATE statement and be significantly faster. — Replace the FindSet() + Modify loop with SalesLine.ModifyAll(\"Unit Price\", NewPrice).", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: bulk_operations — loop + Modify anti-pattern that should use ModifyAll.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-011", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/AgentTaskViewer.Page.al b/src/AgentTaskViewer.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/AgentTaskViewer.Page.al\n@@ -0,0 +1,29 @@\n+page 50302 \"Agent Task Viewer\"\n+{\n+ PageType = List;\n+ SourceTable = \"Agent Task\";\n+ layout\n+ {\n+ area(Content)\n+ {\n+ repeater(Tasks)\n+ {\n+ field(\"Task ID\"; Rec.\"Task ID\") { ApplicationArea = All; ToolTip = 'Specifies the unique identifier of the agent task.'; }\n+ field(Status; Rec.Status) { ApplicationArea = All; ToolTip = 'Specifies the current status of the agent task.'; }\n+ field(InputPreview; InputPreview) { ApplicationArea = All; ToolTip = 'Specifies a preview of the task input data.'; Caption = 'Input Preview'; }\n+ field(OutputPreview; OutputPreview) { ApplicationArea = All; ToolTip = 'Specifies a preview of the task output data.'; Caption = 'Output Preview'; }\n+ }\n+ }\n+ }\n+ trigger OnAfterGetRecord()\n+ begin\n+ Rec.CalcFields(\"Input Data\");\n+ Rec.CalcFields(\"Output Data\");\n+ InputPreview := CopyStr(Rec.GetInputText(), 1, 100);\n+ OutputPreview := CopyStr(Rec.GetOutputText(), 1, 100);\n+ end;\n+\n+ var\n+ InputPreview: Text[100];\n+ OutputPreview: Text[100];\n+}\ndiff --git a/src/BillingOverview.Page.al b/src/BillingOverview.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/BillingOverview.Page.al\n@@ -0,0 +1,25 @@\n+page 50300 \"Billing Overview\"\n+{\n+ PageType = List;\n+ SourceTable = \"Sales Invoice Header\";\n+ layout\n+ {\n+ area(Content)\n+ {\n+ repeater(Lines)\n+ {\n+ field(\"No.\"; Rec.\"No.\") { ApplicationArea = All; ToolTip = 'Specifies the number of the posted sales invoice.'; }\n+ field(\"Sell-to Customer Name\"; Rec.\"Sell-to Customer Name\") { ApplicationArea = All; ToolTip = 'Specifies the name of the customer.'; }\n+ field(Amount; Rec.Amount) { ApplicationArea = All; ToolTip = 'Specifies the invoice amount excluding VAT.'; }\n+ field(\"Amount Including VAT\"; Rec.\"Amount Including VAT\") { ApplicationArea = All; ToolTip = 'Specifies the invoice amount including VAT.'; }\n+ field(\"Remaining Amount\"; Rec.\"Remaining Amount\") { ApplicationArea = All; ToolTip = 'Specifies the remaining unpaid amount.'; }\n+ }\n+ }\n+ }\n+ trigger OnAfterGetRecord()\n+ begin\n+ Rec.CalcFields(Amount);\n+ Rec.CalcFields(\"Amount Including VAT\");\n+ Rec.CalcFields(\"Remaining Amount\");\n+ end;\n+}\ndiff --git a/src/WarehousePickProcessor.Codeunit.al b/src/WarehousePickProcessor.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/WarehousePickProcessor.Codeunit.al\n@@ -0,0 +1,20 @@\n+codeunit 50301 \"Warehouse Pick Processor\"\n+{\n+ procedure ProcessPickLines(WarehouseActivityNo: Code[20])\n+ var\n+ WarehouseActivityLine: Record \"Warehouse Activity Line\";\n+ begin\n+ WarehouseActivityLine.SetRange(\"Activity Type\", WarehouseActivityLine.\"Activity Type\"::Pick);\n+ WarehouseActivityLine.SetRange(\"No.\", WarehouseActivityNo);\n+ if WarehouseActivityLine.FindSet() then\n+ repeat\n+ WarehouseActivityLine.CalcFields(\"Qty. Outstanding (Base)\");\n+ if WarehouseActivityLine.\"Qty. Outstanding (Base)\" > 0 then\n+ ProcessOutstandingLine(WarehouseActivityLine);\n+ until WarehouseActivityLine.Next() = 0;\n+ end;\n+\n+ local procedure ProcessOutstandingLine(var WarehouseActivityLine: Record \"Warehouse Activity Line\")\n+ begin\n+ end;\n+}\n", "expected_comments": [{"file": "src/AgentTaskViewer.Page.al", "line_start": 20, "line_end": 20, "body": "CalcFields on BLOB fields in OnAfterGetRecord is expensive and fires per row. Consider loading BLOBs only when needed or use streaming. — ", "severity": "low", "domain": "performance"}, {"file": "src/BillingOverview.Page.al", "line_start": 21, "line_end": 21, "body": "Multiple CalcFields calls in OnAfterGetRecord trigger can cause N+1 query problems on large datasets. Consider combining into single CalcFields call or use SetLoadFields. — ", "severity": "low", "domain": "performance"}, {"file": "src/WarehousePickProcessor.Codeunit.al", "line_start": 11, "line_end": 11, "body": "CalcFields inside repeat loop creates separate database queries for each record. Use SetLoadFields before FindSet or batch CalcFields operations. — ", "severity": "low", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: CalcFields in loops and OnAfterGetRecord", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-012", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/JournalPostConfirm.Codeunit.al b/src/JournalPostConfirm.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/JournalPostConfirm.Codeunit.al\n@@ -0,0 +1,18 @@\n+codeunit 50331 \"Journal Post Confirm\"\n+{\n+ procedure PostSelectedLines(var GenJournalLine: Record \"Gen. Journal Line\")\n+ begin\n+ GenJournalLine.SetRange(\"Journal Template Name\", GenJournalLine.\"Journal Template Name\");\n+ GenJournalLine.SetRange(\"Journal Batch Name\", GenJournalLine.\"Journal Batch Name\");\n+ if GenJournalLine.FindSet(true) then\n+ repeat\n+ if Confirm(PostLineQst, true, GenJournalLine.\"Line No.\", GenJournalLine.Amount) then begin\n+ GenJournalLine.\"Ready to Post\" := true;\n+ GenJournalLine.Modify();\n+ end;\n+ until GenJournalLine.Next() = 0;\n+ end;\n+\n+ var\n+ PostLineQst: Label 'Post line %1 for amount %2?', Comment = '%1 = line number, %2 = amount';\n+}\ndiff --git a/src/ServiceRegisterBatch.Codeunit.al b/src/ServiceRegisterBatch.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ServiceRegisterBatch.Codeunit.al\n@@ -0,0 +1,14 @@\n+codeunit 50330 \"Service Register Batch\"\n+{\n+ procedure ProcessServiceRegisters()\n+ var\n+ ServiceRegister: Record \"Service Register\";\n+ begin\n+ if ServiceRegister.FindSet(true) then\n+ repeat\n+ ServiceRegister.\"Upgraded\" := true;\n+ ServiceRegister.Modify(false);\n+ Commit();\n+ until ServiceRegister.Next() = 0;\n+ end;\n+}\n", "expected_comments": [{"file": "src/JournalPostConfirm.Codeunit.al", "line_start": 9, "line_end": 9, "body": "Confirm dialog inside loop holds database locks while waiting for user input. Move confirmation outside loop or batch user decisions. — ", "severity": "low", "domain": "performance"}, {"file": "src/ServiceRegisterBatch.Codeunit.al", "line_start": 11, "line_end": 11, "body": "Commit() inside loop creates excessive transaction boundaries and reduces batch performance. Consider committing in batches or after loop completion. — ", "severity": "low", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: Commit in loops and UI blocking operations", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-013", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/AgentActivities.Page.al b/src/AgentActivities.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/AgentActivities.Page.al\n@@ -0,0 +1,36 @@\n+page 50310 \"Agent Activities\"\n+{\n+ PageType = CardPart;\n+ SourceTable = \"Agent Activities Cue\";\n+ layout\n+ {\n+ area(Content)\n+ {\n+ cuegroup(AgentCues)\n+ {\n+ field(HasPending; HasPendingDocs) { ApplicationArea = All; Caption = 'Has Pending'; ToolTip = 'Specifies whether there are pending documents to process.'; }\n+ field(ProcessedToday; ProcessedTodayCount) { ApplicationArea = All; Caption = 'Processed Today'; ToolTip = 'Specifies how many documents were processed today.'; }\n+ }\n+ }\n+ }\n+ trigger OnAfterGetRecord()\n+ begin\n+ CalcCounts();\n+ end;\n+\n+ local procedure CalcCounts()\n+ var\n+ SalesInvHeader: Record \"Sales Invoice Header\";\n+ SalesInvLine: Record \"Sales Invoice Line\";\n+ begin\n+ SalesInvHeader.SetRange(\"Posting Date\", Today);\n+ HasPendingDocs := SalesInvHeader.Count() > 0;\n+\n+ SalesInvLine.SetRange(\"Posting Date\", Today);\n+ ProcessedTodayCount := SalesInvLine.Count();\n+ end;\n+\n+ var\n+ HasPendingDocs: Boolean;\n+ ProcessedTodayCount: Integer;\n+}\ndiff --git a/src/KPICalculator.Codeunit.al b/src/KPICalculator.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/KPICalculator.Codeunit.al\n@@ -0,0 +1,9 @@\n+codeunit 50311 \"KPI Calculator\"\n+{\n+ procedure HasEnoughItems(): Boolean\n+ var\n+ Item: Record Item;\n+ begin\n+ exit(Item.Count() > 0);\n+ end;\n+}\n", "expected_comments": [{"file": "src/AgentActivities.Page.al", "line_start": 27, "line_end": 27, "body": "Count() > 0 on Sales Invoice Header (up to 300k rows) to check existence. Use IsEmpty instead — it stops at the first record found. — ", "severity": "low", "domain": "performance"}, {"file": "src/KPICalculator.Codeunit.al", "line_start": 7, "line_end": 7, "body": "Count() > 0 on Item (up to 800k rows) for a simple existence check. Use not Item.IsEmpty() instead. — ", "severity": "low", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: Count() misuse on large tables", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-014", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/DataProcessor.Codeunit.al b/src/DataProcessor.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/DataProcessor.Codeunit.al\n@@ -0,0 +1,79 @@\n+codeunit 50402 \"Data Processor\"\n+{\n+ Access = Internal;\n+\n+ procedure CalculateTotalsWithTempTable(var SalesLine: Record \"Sales Line\"): Decimal\n+ var\n+ TempItemCache: Record Item temporary;\n+ Item: Record Item;\n+ UnitCost: Decimal;\n+ TotalCost: Decimal;\n+ begin\n+ if SalesLine.FindSet() then\n+ repeat\n+ if not TempItemCache.Get(SalesLine.\"No.\") then begin\n+ Item.SetLoadFields(\"Unit Cost\");\n+ Item.Get(SalesLine.\"No.\");\n+ TempItemCache.Init();\n+ TempItemCache := Item;\n+ TempItemCache.Insert();\n+ end;\n+ UnitCost := TempItemCache.\"Unit Cost\";\n+ TotalCost += SalesLine.Quantity * UnitCost;\n+ until SalesLine.Next() = 0;\n+ exit(TotalCost);\n+ end;\n+\n+ procedure CalculateTotalsWithDictionary(var SalesLine: Record \"Sales Line\"): Decimal\n+ var\n+ UnitCostCache: Dictionary of [Code[20], Decimal];\n+ Item: Record Item;\n+ UnitCost: Decimal;\n+ TotalCost: Decimal;\n+ begin\n+ if SalesLine.FindSet() then\n+ repeat\n+ if not UnitCostCache.ContainsKey(SalesLine.\"No.\") then begin\n+ Item.SetLoadFields(\"Unit Cost\");\n+ Item.Get(SalesLine.\"No.\");\n+ UnitCostCache.Add(SalesLine.\"No.\", Item.\"Unit Cost\");\n+ end;\n+ UnitCost := UnitCostCache.Get(SalesLine.\"No.\");\n+ TotalCost += SalesLine.Quantity * UnitCost;\n+ until SalesLine.Next() = 0;\n+ exit(TotalCost);\n+ end;\n+\n+ procedure BuildCurrencyMap(var CurrencyMap: Dictionary of [Code[10], Text[30]])\n+ var\n+ Currency: Record Currency;\n+ begin\n+ Clear(CurrencyMap);\n+ if Currency.FindSet() then\n+ repeat\n+ CurrencyMap.Add(Currency.Code, Currency.Description);\n+ until Currency.Next() = 0;\n+ end;\n+\n+ procedure GetCustomerCurrency(CustomerNo: Code[20]): Code[10]\n+ var\n+ Customer: Record Customer;\n+ begin\n+ Customer.SetLoadFields(\"Currency Code\");\n+ if Customer.Get(CustomerNo) then\n+ exit(Customer.\"Currency Code\");\n+ exit('');\n+ end;\n+\n+ procedure ApplyDiscounts(var SalesLine: Record \"Sales Line\"; DiscountPct: Decimal)\n+ var\n+ LineAmount: Decimal;\n+ begin\n+ if SalesLine.FindSet(true) then\n+ repeat\n+ LineAmount := SalesLine.\"Line Amount\" * (1 - DiscountPct / 100);\n+ SalesLine.Validate(\"Line Amount\", LineAmount);\n+ SalesLine.Modify(true);\n+ until SalesLine.Next() = 0;\n+ end;\n+}\n", "expected_comments": [{"file": "src/DataProcessor.Codeunit.al", "line_start": 14, "line_end": 14, "body": "Temporary table used as a key-value lookup cache inside a loop. Temp table Get() performs a linear scan, whereas Dictionary of [Code[20], Decimal] provides O(1) lookups. — Replace the temporary table cache with a Dictionary of [Code[20], Decimal]. Use Dictionary.ContainsKey() and Dictionary.Add() for O(1) cache insertions and lookups.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: dictionary_lookup (1 finding). Temporary table used as a lookup cache when Dictionary would provide O(1) lookups instead of linear scans.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-015", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/ContactMgt.Codeunit.al b/src/ContactMgt.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ContactMgt.Codeunit.al\n@@ -0,0 +1,43 @@\n+codeunit 50110 \"Contact Management\"\n+{\n+ Access = Internal;\n+\n+ procedure ContactToVendBusinessRelationExist(ContactNo: Code[20]): Boolean\n+ var\n+ ContactBusinessRelation: Record \"Contact Business Relation\";\n+ begin\n+ if ContactNo = '' then\n+ exit(false);\n+\n+ ContactBusinessRelation.SetRange(\"Contact No.\", ContactNo);\n+ ContactBusinessRelation.SetRange(\"Link to Table\", ContactBusinessRelation.\"Link to Table\"::Vendor);\n+\n+ exit(ContactBusinessRelation.FindFirst());\n+ end;\n+\n+ procedure GetVendorBusinessRelations(ContactNo: Code[20]; var TempContactBusinessRelation: Record \"Contact Business Relation\" temporary)\n+ var\n+ ContactBusinessRelation: Record \"Contact Business Relation\";\n+ begin\n+ TempContactBusinessRelation.DeleteAll();\n+\n+ ContactBusinessRelation.SetRange(\"Contact No.\", ContactNo);\n+ ContactBusinessRelation.SetRange(\"Link to Table\", ContactBusinessRelation.\"Link to Table\"::Vendor);\n+\n+ if ContactBusinessRelation.FindSet() then\n+ repeat\n+ TempContactBusinessRelation := ContactBusinessRelation;\n+ TempContactBusinessRelation.Insert();\n+ until ContactBusinessRelation.Next() = 0;\n+ end;\n+\n+ procedure ValidateContactCompany(ContactNo: Code[20]): Boolean\n+ var\n+ Contact: Record Contact;\n+ begin\n+ Contact.SetLoadFields(Type);\n+ if Contact.Get(ContactNo) then\n+ exit(Contact.Type = Contact.Type::Company);\n+ exit(false);\n+ end;\n+}\ndiff --git a/src/InstallAppCZL.Codeunit.al b/src/InstallAppCZL.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/InstallAppCZL.Codeunit.al\n@@ -0,0 +1,44 @@\n+codeunit 50111 \"Install Application CZL\"\n+{\n+ Access = Internal;\n+\n+ procedure MigrateVATEntries()\n+ var\n+ VATEntry: Record \"VAT Entry\";\n+ ProcessedCount: Integer;\n+ begin\n+ VATEntry.SetRange(\"VAT Bus. Posting Group\", 'DOMESTIC');\n+ VATEntry.SetRange(\"Country/Region Code\", 'CZ');\n+ VATEntry.SetFilter(\"Registration No.\", '<>%1', '');\n+\n+ if VATEntry.IsEmpty() then\n+ exit;\n+\n+ VATEntry.SetLoadFields(\"Registration No.\", \"VAT Registration No.\", \"EU 3-Party Trade\", \"Registration No. CZL\", \"VAT Registration No. CZL\", \"EU 3-Party Trade CZL\");\n+ if VATEntry.FindSet() then\n+ repeat\n+ VATEntry.\"Registration No. CZL\" := VATEntry.\"Registration No.\";\n+ VATEntry.\"VAT Registration No. CZL\" := VATEntry.\"VAT Registration No.\";\n+ VATEntry.\"EU 3-Party Trade CZL\" := VATEntry.\"EU 3-Party Trade\";\n+ VATEntry.Modify(false);\n+\n+ ProcessedCount += 1;\n+\n+ if ProcessedCount mod 1000 = 0 then\n+ LogMigrationProgress(ProcessedCount);\n+\n+ until VATEntry.Next() = 0;\n+\n+ LogMigrationComplete(ProcessedCount);\n+ end;\n+\n+ local procedure LogMigrationProgress(ProcessedCount: Integer)\n+ begin\n+ // Log progress for user feedback\n+ end;\n+\n+ local procedure LogMigrationComplete(TotalProcessed: Integer)\n+ begin\n+ // Log completion statistics\n+ end;\n+}\ndiff --git a/src/ReservationMgt.Codeunit.al b/src/ReservationMgt.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ReservationMgt.Codeunit.al\n@@ -0,0 +1,48 @@\n+codeunit 50112 \"Reservation Priority Mgt.\"\n+{\n+ Access = Internal;\n+\n+ procedure AllocateReservations()\n+ var\n+ ReservWorksheetLine: Record \"Reservation Worksheet Line\";\n+ ProcessedCount: Integer;\n+ TotalToProcess: Integer;\n+ begin\n+ ReservWorksheetLine.SetRange(Status, ReservWorksheetLine.Status::Open);\n+ ReservWorksheetLine.SetRange(\"Priority Level\", 1, 3);\n+\n+ if ReservWorksheetLine.IsEmpty() then\n+ exit;\n+\n+ TotalToProcess := ReservWorksheetLine.Count();\n+\n+ if ReservWorksheetLine.FindSet(true) then begin\n+ if not Confirm(AllocateReservationsQst, false, TotalToProcess) then\n+ exit;\n+\n+ repeat\n+ ReservWorksheetLine.Status := ReservWorksheetLine.Status::Allocated;\n+ ReservWorksheetLine.\"Allocated Date\" := Today();\n+ ReservWorksheetLine.\"Allocated By\" := UserId();\n+ ReservWorksheetLine.Modify();\n+\n+ ProcessedCount += 1;\n+\n+ if ProcessedCount mod 50 = 0 then\n+ UpdateProgressDialog(ProcessedCount, TotalToProcess);\n+\n+ until ReservWorksheetLine.Next() = 0;\n+ end;\n+\n+ Message(AllocatedMsg, ProcessedCount);\n+ end;\n+\n+ local procedure UpdateProgressDialog(Processed: Integer; Total: Integer)\n+ begin\n+ // Update progress indicator\n+ end;\n+\n+ var\n+ AllocateReservationsQst: Label 'Allocate all open reservations (%1 records)?', Comment = '%1 = number of records';\n+ AllocatedMsg: Label 'Successfully allocated %1 reservations.', Comment = '%1 = number of reservations';\n+}\n", "expected_comments": [{"file": "src/ContactMgt.Codeunit.al", "line_start": 15, "line_end": 15, "body": "FindFirst() used for existence check when IsEmpty() would suffice — See agent review for details.", "severity": "medium", "domain": "performance"}, {"file": "src/InstallAppCZL.Codeunit.al", "line_start": 18, "line_end": 18, "body": "FindSet() without true parameter but modifies records in loop — See agent review for details.", "severity": "medium", "domain": "performance"}, {"file": "src/ReservationMgt.Codeunit.al", "line_start": 20, "line_end": 20, "body": "Confirm() dialog after FindSet(true) holds locks — See agent review for details.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: findset_findfirst (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-016", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/ExpenseReportHeader.Table.al b/src/ExpenseReportHeader.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExpenseReportHeader.Table.al\n@@ -0,0 +1,88 @@\n+table 50122 \"Expense Report Header\"\n+{\n+ Caption = 'Expense Report Header';\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"No.\"; Code[20])\n+ {\n+ Caption = 'No.';\n+ }\n+ field(2; Description; Text[100])\n+ {\n+ Caption = 'Description';\n+ }\n+ field(3; Status; Option)\n+ {\n+ Caption = 'Status';\n+ OptionMembers = Open,Submitted,Approved,Rejected;\n+ OptionCaption = 'Open,Submitted,Approved,Rejected';\n+ }\n+ field(4; \"Employee No.\"; Code[20])\n+ {\n+ Caption = 'Employee No.';\n+ TableRelation = Employee;\n+ }\n+ field(5; \"Report Date\"; Date)\n+ {\n+ Caption = 'Report Date';\n+ }\n+ field(6; \"Department Code\"; Code[20])\n+ {\n+ Caption = 'Department Code';\n+ TableRelation = \"Dimension Value\".Code where(\"Dimension Code\" = const('DEPARTMENT'));\n+ }\n+ field(7; \"Currency Code\"; Code[10])\n+ {\n+ Caption = 'Currency Code';\n+ TableRelation = Currency;\n+ }\n+ field(8; \"Total Amount\"; Decimal)\n+ {\n+ Caption = 'Total Amount';\n+ }\n+ field(10; \"Refundable Amount\"; Decimal)\n+ {\n+ FieldClass = FlowField;\n+ Caption = 'Refundable Amount';\n+ CalcFormula = sum(\"Expense Report Line\".Amount\n+ where(\"Document No.\" = field(\"No.\"),\n+ Refundable = const(true)));\n+ Editable = false;\n+ }\n+ field(11; \"Non-Refundable Amount\"; Decimal)\n+ {\n+ FieldClass = FlowField;\n+ Caption = 'Non-Refundable Amount';\n+ CalcFormula = sum(\"Expense Report Line\".Amount\n+ where(\"Document No.\" = field(\"No.\"),\n+ Refundable = const(false)));\n+ Editable = false;\n+ }\n+ field(12; \"Submitted Date\"; DateTime)\n+ {\n+ Caption = 'Submitted Date';\n+ Editable = false;\n+ }\n+ field(13; \"Approved Date\"; DateTime)\n+ {\n+ Caption = 'Approved Date';\n+ Editable = false;\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"No.\") { Clustered = true; }\n+ key(Key2; \"Employee No.\", \"Report Date\") { }\n+ key(Key3; Status, \"Report Date\") { }\n+ key(Key4; \"Department Code\") { }\n+ }\n+\n+ trigger OnInsert()\n+ begin\n+ \"Report Date\" := Today();\n+ Status := Status::Open;\n+ end;\n+}\n", "expected_comments": [{"file": "src/ExpenseReportHeader.Table.al", "line_start": 47, "line_end": 47, "body": "FlowField CalcFormula uses SUM on Expense Report Line without a matching SIFT key, causing table scans on large datasets — See agent review for details.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: index_usage — FlowField CalcFormula missing SIFT key on source table", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-017", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/AgentStatus.Table.al b/src/AgentStatus.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/AgentStatus.Table.al\n@@ -0,0 +1,85 @@\n+table 50130 \"EA Agent Status\"\n+{\n+ Caption = 'Agent Run Status';\n+ DataClassification = SystemMetadata;\n+\n+ fields\n+ {\n+ field(1; \"Primary Key\"; Code[10])\n+ {\n+ Caption = 'Primary Key';\n+ }\n+ field(2; \"Last Run\"; DateTime)\n+ {\n+ Caption = 'Last Run';\n+ }\n+ field(3; \"Notifications Enabled\"; Boolean)\n+ {\n+ Caption = 'Notifications Enabled';\n+ }\n+ field(4; Status; Enum \"Agent Run State\")\n+ {\n+ Caption = 'Status';\n+ }\n+ field(5; \"Last Error Message\"; Text[250])\n+ {\n+ Caption = 'Last Error Message';\n+ DataClassification = CustomerContent;\n+ }\n+ field(6; \"Retry Count\"; Integer)\n+ {\n+ Caption = 'Retry Count';\n+ }\n+ field(7; \"Next Retry Time\"; DateTime)\n+ {\n+ Caption = 'Next Retry Time';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Primary Key\") { Clustered = true; }\n+ }\n+\n+ procedure GetOrCreate(): Record \"EA Agent Status\"\n+ begin\n+ Rec.LockTable();\n+ if not Rec.Get() then begin\n+ Rec.Init();\n+ Rec.\"Primary Key\" := '';\n+ Rec.\"Notifications Enabled\" := true;\n+ Rec.Status := Rec.Status::Idle;\n+ Rec.\"Last Run\" := CurrentDateTime();\n+ Rec.Insert();\n+ end;\n+ exit(Rec);\n+ end;\n+\n+ procedure ShouldRunNotifications(): Boolean\n+ begin\n+ exit(GetOrCreate().\"Notifications Enabled\");\n+ end;\n+\n+ procedure UpdateStatus(NewStatus: Enum \"Agent Run State\"; ErrorMessage: Text[250])\n+ var\n+ AgentStatus: Record \"EA Agent Status\";\n+ begin\n+ AgentStatus := GetOrCreate();\n+ AgentStatus.Status := NewStatus;\n+ AgentStatus.\"Last Run\" := CurrentDateTime();\n+ AgentStatus.\"Last Error Message\" := ErrorMessage;\n+ if NewStatus = AgentStatus.Status::Error then\n+ AgentStatus.\"Retry Count\" += 1\n+ else\n+ AgentStatus.\"Retry Count\" := 0;\n+ AgentStatus.Modify();\n+ end;\n+\n+ procedure IsRunning(): Boolean\n+ var\n+ AgentStatus: Record \"EA Agent Status\";\n+ begin\n+ AgentStatus := GetOrCreate();\n+ exit(AgentStatus.Status = AgentStatus.Status::Running);\n+ end;\n+}\ndiff --git a/src/VendEntryEditHandler.Codeunit.al b/src/VendEntryEditHandler.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/VendEntryEditHandler.Codeunit.al\n@@ -0,0 +1,37 @@\n+codeunit 50131 \"Vend. Entry Edit Handler\"\n+{\n+ Access = Internal;\n+\n+ procedure UpdateRelatedAdvanceLetterEntries(EntryNo: Integer; Level: Integer)\n+ var\n+ VendLedgerEntry: Record \"Vendor Ledger Entry\";\n+ RelatedEntry: Record \"Vendor Ledger Entry\";\n+ MaxRecursionLevel: Integer;\n+ begin\n+ MaxRecursionLevel := 50;\n+ if Level > MaxRecursionLevel then\n+ Error(MaxRecursionErr, EntryNo);\n+\n+ VendLedgerEntry.SetLoadFields(\"Letter No.\", \"Letter Line No.\", \"Advance Letter Template Code\", \"Document No.\", \"Vendor No.\");\n+ VendLedgerEntry.ReadIsolation(IsolationLevel::UpdLock);\n+ if not VendLedgerEntry.Get(EntryNo) then\n+ exit;\n+\n+ VendLedgerEntry.\"Letter No.\" := '';\n+ VendLedgerEntry.\"Letter Line No.\" := 0;\n+ VendLedgerEntry.\"Advance Letter Template Code\" := '';\n+ VendLedgerEntry.Modify();\n+\n+ RelatedEntry.SetLoadFields(\"Entry No.\");\n+ RelatedEntry.SetRange(\"Closed by Entry No.\", EntryNo);\n+ RelatedEntry.SetRange(Open, false);\n+\n+ if RelatedEntry.FindSet() then\n+ repeat\n+ UpdateRelatedAdvanceLetterEntries(RelatedEntry.\"Entry No.\", Level + 1);\n+ until RelatedEntry.Next() = 0;\n+ end;\n+\n+ var\n+ MaxRecursionErr: Label 'Maximum recursion level exceeded for entry %1', Comment = '%1 = entry number';\n+}\ndiff --git a/src/AgentRunState.Enum.al b/src/AgentRunState.Enum.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/AgentRunState.Enum.al\n@@ -0,0 +1,8 @@\n+enum 50132 \"Agent Run State\"\n+{\n+ Extensible = false;\n+\n+ value(0; Idle) { Caption = 'Idle'; }\n+ value(1; Running) { Caption = 'Running'; }\n+ value(2; Error) { Caption = 'Error'; }\n+}\n", "expected_comments": [{"file": "src/AgentStatus.Table.al", "line_start": 46, "line_end": 46, "body": "GetOrCreate() unconditionally locks even for readers — See agent review for details.", "severity": "medium", "domain": "performance"}, {"file": "src/VendEntryEditHandler.Codeunit.al", "line_start": 16, "line_end": 16, "body": "ReadIsolation UpdLock inside recursive function — See agent review for details.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: locking (2 findings). Agent correctly identified these and developers fixed them.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-018", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/BOMBufferMgt.Codeunit.al b/src/BOMBufferMgt.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/BOMBufferMgt.Codeunit.al\n@@ -0,0 +1,68 @@\n+codeunit 50142 \"BOM Buffer Management\"\n+{\n+ Access = Internal;\n+\n+ procedure CalcTotalCost(var BOMBuffer: Record \"BOM Buffer\")\n+ var\n+ Item: Record Item;\n+ TotalCost: Decimal;\n+ LineCount: Integer;\n+ begin\n+ // Initialize calculation\n+ TotalCost := 0;\n+ LineCount := 0;\n+\n+ // Validate BOM buffer has records\n+ if BOMBuffer.IsEmpty() then\n+ exit;\n+\n+ if BOMBuffer.FindSet() then\n+ repeat\n+ LineCount += 1;\n+\n+ if Item.Get(BOMBuffer.\"No.\") then begin\n+ // Calculate cost based on costing method\n+ if ShouldUseStandardCost(Item) then\n+ TotalCost += Item.\"Standard Cost\" * BOMBuffer.Quantity\n+ else if ShouldUseAverageCost(Item) then\n+ TotalCost += Item.\"Unit Cost\" * BOMBuffer.Quantity\n+ else\n+ TotalCost += GetLastDirectCost(Item) * BOMBuffer.Quantity;\n+ end;\n+\n+ // Update line with calculated cost\n+ UpdateBOMLineWithCost(BOMBuffer, Item);\n+\n+ until BOMBuffer.Next() = 0;\n+\n+ // Log calculation summary\n+ LogCostCalculation(LineCount, TotalCost);\n+ end;\n+\n+ local procedure ShouldUseStandardCost(Item: Record Item): Boolean\n+ begin\n+ exit(Item.\"Costing Method\" = Item.\"Costing Method\"::Standard);\n+ end;\n+\n+ local procedure ShouldUseAverageCost(Item: Record Item): Boolean\n+ begin\n+ exit(Item.\"Costing Method\" = Item.\"Costing Method\"::Average);\n+ end;\n+\n+ local procedure GetLastDirectCost(Item: Record Item): Decimal\n+ begin\n+ exit(Item.\"Last Direct Cost\");\n+ end;\n+\n+ local procedure UpdateBOMLineWithCost(var BOMBuffer: Record \"BOM Buffer\"; Item: Record Item)\n+ begin\n+ BOMBuffer.\"Unit Cost\" := Item.\"Unit Cost\";\n+ BOMBuffer.\"Total Cost\" := BOMBuffer.\"Unit Cost\" * BOMBuffer.Quantity;\n+ BOMBuffer.Modify();\n+ end;\n+\n+ local procedure LogCostCalculation(LineCount: Integer; TotalCost: Decimal)\n+ begin\n+ // Log calculation summary for audit\n+ end;\n+}\ndiff --git a/src/PurchRcptLineMgt.Codeunit.al b/src/PurchRcptLineMgt.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PurchRcptLineMgt.Codeunit.al\n@@ -0,0 +1,62 @@\n+codeunit 50140 \"Purch. Rcpt. Line Mgmt.\"\n+{\n+ Access = Internal;\n+\n+ procedure ProcessReceiptLines(OrderNo: Code[20])\n+ var\n+ PurchRcptLine: Record \"Purch. Rcpt. Line\";\n+ ProcessedCount: Integer;\n+ begin\n+ // Validate order number\n+ if OrderNo = '' then\n+ exit;\n+\n+ // Setup filters for receipt lines\n+ PurchRcptLine.SetRange(\"Order No.\", OrderNo);\n+ PurchRcptLine.SetRange(Type, PurchRcptLine.Type::Item);\n+ PurchRcptLine.SetFilter(Quantity, '>0');\n+\n+ if PurchRcptLine.IsEmpty() then\n+ exit;\n+\n+ if PurchRcptLine.FindSet() then\n+ repeat\n+ PurchRcptLine.CalcFields(\"Currency Code\");\n+\n+ if PurchRcptLine.\"Currency Code\" <> '' then begin\n+ ProcessForeignCurrencyLine(PurchRcptLine);\n+ ProcessedCount += 1;\n+ end else\n+ ProcessLocalCurrencyLine(PurchRcptLine);\n+\n+ // Update line status\n+ UpdateLineProcessingStatus(PurchRcptLine);\n+\n+ until PurchRcptLine.Next() = 0;\n+\n+ // Log processing completion\n+ LogProcessingComplete(OrderNo, ProcessedCount);\n+ end;\n+\n+ local procedure ProcessForeignCurrencyLine(PurchRcptLine: Record \"Purch. Rcpt. Line\")\n+ begin\n+ // Process foreign currency line with exchange rate calculations\n+ end;\n+\n+ local procedure ProcessLocalCurrencyLine(PurchRcptLine: Record \"Purch. Rcpt. Line\")\n+ begin\n+ // Process local currency line\n+ end;\n+\n+ local procedure UpdateLineProcessingStatus(var PurchRcptLine: Record \"Purch. Rcpt. Line\")\n+ begin\n+ // Update processing flags\n+ PurchRcptLine.\"Processed Date\" := Today();\n+ PurchRcptLine.Modify();\n+ end;\n+\n+ local procedure LogProcessingComplete(OrderNo: Code[20]; ProcessedCount: Integer)\n+ begin\n+ // Log completion statistics\n+ end;\n+}\ndiff --git a/src/CustomReportLayoutsList.Page.al b/src/CustomReportLayoutsList.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomReportLayoutsList.Page.al\n@@ -0,0 +1,80 @@\n+page 50141 \"Custom Report Layouts List\"\n+{\n+ Caption = 'Custom Report Layouts List';\n+ PageType = List;\n+ SourceTable = \"Custom Report Layout\";\n+ CardPageId = \"Custom Report Layout\";\n+ Editable = false;\n+\n+ layout\n+ {\n+ area(Content)\n+ {\n+ repeater(Lines)\n+ {\n+ field(\"Code\"; Rec.\"Code\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the ID of the custom report layout.';\n+ }\n+ field(Description; Rec.Description)\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies a description of the custom report layout.';\n+ }\n+ field(\"Report ID\"; Rec.\"Report ID\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the ID of the report.';\n+ }\n+ field(\"Report Name\"; Rec.\"Report Name\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the name of the report.';\n+ }\n+ field(\"Layout Format\"; Rec.\"Layout Format\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the format of the layout (Word or RDLC).';\n+ }\n+ field(UserDisplayName; UserDisplayName)\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Modified By';\n+ ToolTip = 'Specifies who last modified this layout.';\n+ }\n+ }\n+ }\n+ }\n+\n+ trigger OnAfterGetRecord()\n+ begin\n+ UpdateUserDisplayName();\n+ end;\n+\n+ local procedure UpdateUserDisplayName()\n+ var\n+ User: Record User;\n+ begin\n+ UserDisplayName := '';\n+\n+ // Try to get the user who last modified the record\n+ if Rec.\"Last Modified by User\" <> '' then\n+ if User.Get(Rec.\"Last Modified by User\") then\n+ UserDisplayName := User.\"Full Name\";\n+\n+ // Fallback to created by user if last modified is empty\n+ if UserDisplayName = '' then\n+ if Rec.\"Created by User\" <> '' then\n+ if User.Get(Rec.\"Created by User\") then\n+ UserDisplayName := User.\"Full Name\";\n+\n+ // Final fallback\n+ if UserDisplayName = '' then\n+ UserDisplayName := UnknownUserLbl;\n+ end;\n+\n+ var\n+ UserDisplayName: Text[80];\n+ UnknownUserLbl: Label 'Unknown User';\n+}\n", "expected_comments": [{"file": "src/BOMBufferMgt.Codeunit.al", "line_start": 23, "line_end": 23, "body": "N+1 Item.Get per BOM line — See agent review for details.", "severity": "medium", "domain": "performance"}, {"file": "src/PurchRcptLineMgt.Codeunit.al", "line_start": 24, "line_end": 24, "body": "CalcFields on FlowField inside a loop — See agent review for details.", "severity": "medium", "domain": "performance"}, {"file": "src/CustomReportLayoutsList.Page.al", "line_start": 52, "line_end": 52, "body": "N+1 User.Get() in OnAfterGetRecord — See agent review for details.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: loop_optimization (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-019", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/RequisitionWorksheetName.Table.al b/src/RequisitionWorksheetName.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/RequisitionWorksheetName.Table.al\n@@ -0,0 +1,88 @@\n+table 50151 \"Requisition Worksheet Name\"\n+{\n+ Caption = 'Requisition Worksheet Name';\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"Worksheet Template Name\"; Code[10])\n+ {\n+ Caption = 'Worksheet Template Name';\n+ TableRelation = \"Req. Wksh. Template\";\n+ }\n+ field(2; Name; Code[10])\n+ {\n+ Caption = 'Name';\n+ NotBlank = true;\n+ }\n+ field(3; Description; Text[100])\n+ {\n+ Caption = 'Description';\n+ }\n+ field(4; \"Template Type\"; Option)\n+ {\n+ Caption = 'Template Type';\n+ OptionMembers = \"Req.\",\"For. Labor\",Planning;\n+ OptionCaption = 'Req.,For. Labor,Planning';\n+ }\n+ field(5; \"Location Code\"; Code[10])\n+ {\n+ Caption = 'Location Code';\n+ TableRelation = Location.Code where(\"Use As In-Transit\" = const(false));\n+ }\n+ field(6; Recurring; Boolean)\n+ {\n+ Caption = 'Recurring';\n+ }\n+ field(10; \"Total Quantity\"; Decimal)\n+ {\n+ FieldClass = FlowField;\n+ Caption = 'Total Quantity';\n+ CalcFormula = sum(\"Requisition Line\".Quantity\n+ where(\"Worksheet Template Name\" = field(\"Worksheet Template Name\"),\n+ \"Journal Batch Name\" = field(Name)));\n+ Editable = false;\n+ }\n+ field(11; \"Total Cost\"; Decimal)\n+ {\n+ FieldClass = FlowField;\n+ Caption = 'Total Cost';\n+ CalcFormula = sum(\"Requisition Line\".\"Total Cost (LCY)\"\n+ where(\"Worksheet Template Name\" = field(\"Worksheet Template Name\"),\n+ \"Journal Batch Name\" = field(Name)));\n+ Editable = false;\n+ }\n+ field(13; \"Planning Flexibility\"; Option)\n+ {\n+ Caption = 'Planning Flexibility';\n+ OptionMembers = Unlimited,None;\n+ OptionCaption = 'Unlimited,None';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Worksheet Template Name\", Name) { Clustered = true; }\n+ key(Key2; \"Template Type\", Name) { }\n+ key(Key3; \"Location Code\") { }\n+ }\n+\n+ trigger OnInsert()\n+ begin\n+ \"Template Type\" := \"Template Type\"::\"Req.\";\n+ \"Planning Flexibility\" := \"Planning Flexibility\"::Unlimited;\n+ end;\n+\n+ procedure GetWorksheetTotals(var TotalQuantity: Decimal; var TotalCost: Decimal)\n+ begin\n+ CalcFields(\"Total Quantity\", \"Total Cost\");\n+ TotalQuantity := \"Total Quantity\";\n+ TotalCost := \"Total Cost\";\n+ end;\n+\n+ procedure GetWorksheetCost(): Decimal\n+ begin\n+ CalcFields(\"Total Cost\");\n+ exit(\"Total Cost\");\n+ end;\n+}\n", "expected_comments": [{"file": "src/RequisitionWorksheetName.Table.al", "line_start": 78, "line_end": 78, "body": "CalcFields on SUM FlowFields over Requisition Line can table-scan without a supporting SIFT path — See agent review for details.", "severity": "medium", "domain": "performance"}, {"file": "src/RequisitionWorksheetName.Table.al", "line_start": 85, "line_end": 85, "body": "CalcFields on SUM FlowFields over Requisition Line can table-scan without a supporting SIFT path — See agent review for details.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: other_performance — SUM FlowFields without SIFT indexes", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-020", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/AssemblyOrderSubform.Page.al b/src/AssemblyOrderSubform.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/AssemblyOrderSubform.Page.al\n@@ -0,0 +1,76 @@\n+page 50160 \"Assembly Order Subform Sample\"\n+{\n+ Caption = 'Assembly Order Subform Sample';\n+ PageType = ListPart;\n+ SourceTable = \"Assembly Line\";\n+ AutoSplitKey = true;\n+ DelayedInsert = true;\n+\n+ layout\n+ {\n+ area(Content)\n+ {\n+ repeater(Lines)\n+ {\n+ field(\"No.\"; Rec.\"No.\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the number of the component item.';\n+ }\n+ field(Description; Rec.Description)\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the description of the assembly component.';\n+ }\n+ field(Type; Rec.Type)\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies if the assembly component is an item or a resource.';\n+ }\n+ field(\"Unit of Measure Code\"; Rec.\"Unit of Measure Code\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the unit of measure code for the assembly component.';\n+ }\n+ field(Quantity; Rec.Quantity)\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies how many units of the assembly component are expected on this assembly order line.';\n+ }\n+ field(\"Quantity per\"; Rec.\"Quantity per\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies how many units of the component are needed to assemble one unit of the parent item.';\n+ }\n+ field(AvailWarning; AvailWarning)\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Availability Warning';\n+ ToolTip = 'Specifies if there is an availability warning for this component.';\n+ Editable = false;\n+ Style = Attention;\n+ StyleExpr = AvailWarning;\n+ }\n+ }\n+ }\n+ }\n+\n+ trigger OnAfterGetRecord()\n+ begin\n+ UpdateAndPersistAvailWarning();\n+ end;\n+\n+ local procedure UpdateAndPersistAvailWarning()\n+ begin\n+ AvailWarning := (Rec.Type = Rec.Type::Item) and (Rec.Quantity > Rec.\"Quantity per\");\n+\n+ if Rec.\"Avail. Warning\" <> AvailWarning then begin\n+ Rec.\"Avail. Warning\" := AvailWarning;\n+ Rec.\"Last Availability Check\" := CurrentDateTime();\n+ Rec.Modify();\n+ end;\n+ end;\n+\n+ var\n+ AvailWarning: Boolean;\n+}\ndiff --git a/src/ItemLedgerEntryAPAC.Table.al b/src/ItemLedgerEntryAPAC.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ItemLedgerEntryAPAC.Table.al\n@@ -0,0 +1,144 @@\n+table 50161 \"Item Ledger Entry APAC\"\n+{\n+ Caption = 'Item Ledger Entry APAC';\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"Entry No.\"; Integer)\n+ {\n+ Caption = 'Entry No.';\n+ AutoIncrement = true;\n+ }\n+ field(2; \"Item No.\"; Code[20])\n+ {\n+ Caption = 'Item No.';\n+ TableRelation = Item;\n+ }\n+ field(3; \"Posting Date\"; Date)\n+ {\n+ Caption = 'Posting Date';\n+ }\n+ field(4; \"Entry Type\"; Option)\n+ {\n+ Caption = 'Entry Type';\n+ OptionMembers = Purchase,Sale,\"Positive Adjmt.\",\"Negative Adjmt.\",Transfer,Consumption,Output,\"Assembly Consumption\",\"Assembly Output\";\n+ OptionCaption = 'Purchase,Sale,Positive Adjmt.,Negative Adjmt.,Transfer,Consumption,Output,Assembly Consumption,Assembly Output';\n+ }\n+ field(5; \"Source No.\"; Code[20])\n+ {\n+ Caption = 'Source No.';\n+ }\n+ field(6; \"Document No.\"; Code[20])\n+ {\n+ Caption = 'Document No.';\n+ }\n+ field(7; \"Location Code\"; Code[10])\n+ {\n+ Caption = 'Location Code';\n+ TableRelation = Location;\n+ }\n+ field(8; \"Variant Code\"; Code[10])\n+ {\n+ Caption = 'Variant Code';\n+ TableRelation = \"Item Variant\".Code where(\"Item No.\" = field(\"Item No.\"));\n+ }\n+ field(9; Description; Text[100])\n+ {\n+ Caption = 'Description';\n+ }\n+ field(10; \"Unit of Measure Code\"; Code[10])\n+ {\n+ Caption = 'Unit of Measure Code';\n+ TableRelation = \"Unit of Measure\";\n+ }\n+ field(11; Quantity; Decimal)\n+ {\n+ Caption = 'Quantity';\n+ DecimalPlaces = 0 : 5;\n+ }\n+ field(12; \"Remaining Quantity\"; Decimal)\n+ {\n+ Caption = 'Remaining Quantity';\n+ DecimalPlaces = 0 : 5;\n+ }\n+ field(13; \"Invoiced Quantity\"; Decimal)\n+ {\n+ Caption = 'Invoiced Quantity';\n+ DecimalPlaces = 0 : 5;\n+ }\n+ field(14; Open; Boolean)\n+ {\n+ Caption = 'Open';\n+ }\n+ field(15; Positive; Boolean)\n+ {\n+ Caption = 'Positive';\n+ }\n+ field(20; \"APAC Region Code\"; Code[10])\n+ {\n+ Caption = 'APAC Region Code';\n+ }\n+ field(21; \"APAC Country Code\"; Code[10])\n+ {\n+ Caption = 'APAC Country Code';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Entry No.\") { Clustered = true; }\n+ key(Key2; \"Item No.\", \"Posting Date\") { }\n+ key(Key3; \"Item No.\", Open, \"Variant Code\", \"Unit of Measure Code\", \"Location Code\", \"Posting Date\") { }\n+ key(Key4; \"Source No.\", \"Item No.\", \"Variant Code\", \"Posting Date\") { }\n+ key(Key5; \"Item No.\", \"Entry Type\", \"Variant Code\", \"Drop Shipment\", \"Location Code\", \"Posting Date\") { }\n+ key(Key6; \"Item No.\", Open, \"Variant Code\", Positive, \"Location Code\", \"Posting Date\") { }\n+ key(Key7; \"Location Code\", \"Item No.\", \"Variant Code\", Open, Positive)\n+ {\n+ IncludedFields = Quantity, \"Remaining Quantity\";\n+ }\n+ key(Key8; \"Country/Region Code\", \"Entry Type\", \"Posting Date\") { }\n+ key(Key9; \"Document No.\", \"Document Type\", \"Location Code\") { }\n+ key(Key10; \"Item No.\", \"APAC Region Code\", \"Posting Date\") { }\n+ }\n+\n+ trigger OnInsert()\n+ begin\n+ if \"Posting Date\" = 0D then\n+ \"Posting Date\" := Today();\n+\n+ if \"APAC Country Code\" = '' then\n+ \"APAC Country Code\" := GetCountryFromLocation(\"Location Code\");\n+\n+ if \"APAC Region Code\" = '' then\n+ \"APAC Region Code\" := GetRegionFromCountry(\"APAC Country Code\");\n+ end;\n+\n+ local procedure GetCountryFromLocation(LocationCode: Code[10]): Code[10]\n+ var\n+ Location: Record Location;\n+ begin\n+ if Location.Get(LocationCode) then\n+ exit(Location.\"Country/Region Code\");\n+ exit('');\n+ end;\n+\n+ local procedure GetRegionFromCountry(CountryCode: Code[10]): Code[10]\n+ begin\n+ // Map country to APAC region\n+ case CountryCode of\n+ 'AU':\n+ exit('OCEANIA');\n+ 'JP', 'KR':\n+ exit('NORTHEAST');\n+ 'CN', 'HK', 'TW':\n+ exit('CHINA');\n+ 'TH', 'SG', 'MY':\n+ exit('SOUTHEAST');\n+ 'IN':\n+ exit('SOUTH');\n+ else\n+ exit('OTHER');\n+ end;\n+ end;\n+}\n", "expected_comments": [{"file": "src/AssemblyOrderSubform.Page.al", "line_start": 70, "line_end": 70, "body": "Modify in OnAfterGetRecord — See agent review for details.", "severity": "medium", "domain": "performance"}, {"file": "src/ItemLedgerEntryAPAC.Table.al", "line_start": 96, "line_end": 96, "body": "SumIndexFields changed to IncludedFields — See agent review for details.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: query_optimization (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-021", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/ConfigurationHelper.Codeunit.al b/src/ConfigurationHelper.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ConfigurationHelper.Codeunit.al\n@@ -0,0 +1,77 @@\n+codeunit 50400 \"Configuration Helper\"\n+{\n+ Access = Internal;\n+\n+ procedure GetOrCreateSetup(): Record \"General Ledger Setup\"\n+ var\n+ GLSetup: Record \"General Ledger Setup\";\n+ begin\n+ GLSetup.LockTable();\n+ if not GLSetup.Get() then begin\n+ GLSetup.Init();\n+ GLSetup.\"Allow Posting From\" := CalcDate('<-CM>', WorkDate());\n+ GLSetup.\"Allow Posting To\" := CalcDate('', WorkDate());\n+ GLSetup.Insert(true);\n+ end;\n+ exit(GLSetup);\n+ end;\n+\n+ procedure GetCustomerDisplayName(CustomerNo: Code[20]): Text[100]\n+ var\n+ Customer: Record Customer;\n+ begin\n+ Customer.LockTable();\n+ Customer.SetLoadFields(Name);\n+ Customer.SetRange(\"No.\", CustomerNo);\n+ Customer.SetRange(Blocked, Customer.Blocked::\" \");\n+ if Customer.FindFirst() then\n+ exit(Customer.Name);\n+ exit('');\n+ end;\n+\n+ procedure GetItemDescription(ItemNo: Code[20]): Text[100]\n+ var\n+ Item: Record Item;\n+ begin\n+ Item.ReadIsolation := IsolationLevel::ReadCommitted;\n+ Item.SetLoadFields(Description);\n+ if Item.Get(ItemNo) then\n+ exit(Item.Description);\n+ exit('');\n+ end;\n+\n+ procedure GetCurrencyExchangeRate(CurrencyCode: Code[10]; PostingDate: Date): Decimal\n+ var\n+ CurrencyExchangeRate: Record \"Currency Exchange Rate\";\n+ begin\n+ CurrencyExchangeRate.SetRange(\"Currency Code\", CurrencyCode);\n+ CurrencyExchangeRate.SetRange(\"Starting Date\", 0D, PostingDate);\n+ if CurrencyExchangeRate.FindLast() then\n+ exit(CurrencyExchangeRate.\"Exchange Rate Amount\");\n+ exit(1);\n+ end;\n+\n+ procedure GetDefaultDimension(TableID: Integer; No: Code[20]): Code[20]\n+ var\n+ DefaultDimension: Record \"Default Dimension\";\n+ begin\n+ DefaultDimension.SetRange(\"Table ID\", TableID);\n+ DefaultDimension.SetRange(\"No.\", No);\n+ DefaultDimension.SetRange(\"Dimension Code\", 'DEPARTMENT');\n+ if DefaultDimension.FindFirst() then\n+ exit(DefaultDimension.\"Dimension Value Code\");\n+ exit('');\n+ end;\n+\n+ procedure IsFeatureEnabled(FeatureKey: Text[50]): Boolean\n+ var\n+ FeatureDataUpdateStatus: Record \"Feature Data Update Status\";\n+ begin\n+ FeatureDataUpdateStatus.ReadIsolation := IsolationLevel::ReadCommitted;\n+ FeatureDataUpdateStatus.SetRange(\"Feature Key\", FeatureKey);\n+ if FeatureDataUpdateStatus.FindFirst() then\n+ exit(FeatureDataUpdateStatus.\"Feature Status\" = FeatureDataUpdateStatus.\"Feature Status\"::Enabled);\n+ exit(false);\n+ end;\n+}\n+\n", "expected_comments": [{"file": "src/ConfigurationHelper.Codeunit.al", "line_start": 9, "line_end": 9, "body": "LockTable() in GetOrCreate pattern where most callers only read. The Insert path is rarely hit after initial setup, but every caller pays the lock cost. — Use ReadIsolation := IsolationLevel::ReadCommitted for the initial Get(), then only escalate to LockTable if the record does not exist and an Insert is needed.", "severity": "medium", "domain": "performance"}, {"file": "src/ConfigurationHelper.Codeunit.al", "line_start": 23, "line_end": 23, "body": "LockTable() before a read-only FindFirst that only retrieves data for display. No modification follows, so the lock is unnecessary and blocks other transactions. — Remove the LockTable() call or use ReadIsolation := IsolationLevel::ReadCommitted since this procedure only reads data for display purposes.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: readisolation (2 findings). LockTable used for read-only operations where ReadIsolation would avoid unnecessary locking overhead.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-022", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/AssemblyLineMgt.Codeunit.al b/src/AssemblyLineMgt.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/AssemblyLineMgt.Codeunit.al\n@@ -0,0 +1,33 @@\n+codeunit 50172 \"Assembly Line Check\"\n+{\n+ Access = Internal;\n+\n+ procedure CheckAvailability(AssemblyLine: Record \"Assembly Line\"): Boolean\n+ var\n+ AssemblyLineCheck: Record \"Assembly Line\";\n+ begin\n+ AssemblyLineCheck.Get(AssemblyLine.\"Document Type\", AssemblyLine.\"Document No.\", AssemblyLine.\"Line No.\");\n+\n+ if AssemblyLineCheck.Quantity <= 0 then\n+ exit(false);\n+\n+ if AssemblyLineCheck.Type <> AssemblyLineCheck.Type::Item then\n+ exit(true);\n+\n+ exit(AssemblyLineCheck.\"No.\" <> '');\n+ end;\n+\n+ procedure ValidateAssemblyLineQuantity(var AssemblyLine: Record \"Assembly Line\")\n+ begin\n+ if AssemblyLine.Quantity <= 0 then\n+ Error(QuantityZeroErr);\n+\n+ if AssemblyLine.Type = AssemblyLine.Type::Item then\n+ if not CheckAvailability(AssemblyLine) then\n+ Message(InsufficientInventoryMsg, AssemblyLine.\"No.\");\n+ end;\n+\n+ var\n+ QuantityZeroErr: Label 'Quantity must be greater than zero';\n+ InsufficientInventoryMsg: Label 'Insufficient inventory available for item %1', Comment = '%1 = item number';\n+}\ndiff --git a/src/PurchAllocAccMgt.Codeunit.al b/src/PurchAllocAccMgt.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PurchAllocAccMgt.Codeunit.al\n@@ -0,0 +1,65 @@\n+codeunit 50170 \"Purchase Alloc. Acc. Mgt.\"\n+{\n+ Access = Internal;\n+\n+ procedure DistributeAmount(PurchaseLine: Record \"Purchase Line\")\n+ var\n+ PurchaseHeader: Record \"Purchase Header\";\n+ AllocationAccount: Record \"Allocation Account\";\n+ begin\n+ PurchaseHeader.Get(PurchaseLine.\"Document Type\", PurchaseLine.\"Document No.\");\n+\n+ if PurchaseLine.\"Selected Alloc. Account No.\" = '' then\n+ exit;\n+\n+ // Validate allocation account exists\n+ if not AllocationAccount.Get(PurchaseLine.\"Selected Alloc. Account No.\") then\n+ Error(AllocAccNotExistErr, PurchaseLine.\"Selected Alloc. Account No.\");\n+\n+ // Validate account is active\n+ AllocationAccount.TestField(Blocked, false);\n+\n+ // Perform the distribution\n+ DistributeToAllocAccount(PurchaseLine, AllocationAccount);\n+\n+ // Update line with distribution status\n+ UpdateLineDistributionStatus(PurchaseLine);\n+ end;\n+\n+ procedure ValidateAllocationSetup(PurchaseLine: Record \"Purchase Line\"): Boolean\n+ var\n+ AllocationAccount: Record \"Allocation Account\";\n+ begin\n+ if PurchaseLine.\"Selected Alloc. Account No.\" = '' then\n+ exit(false);\n+\n+ if not AllocationAccount.Get(PurchaseLine.\"Selected Alloc. Account No.\") then\n+ exit(false);\n+\n+ exit(not AllocationAccount.Blocked);\n+ end;\n+\n+ local procedure DistributeToAllocAccount(PurchaseLine: Record \"Purchase Line\"; AllocationAccount: Record \"Allocation Account\")\n+ var\n+ PurchaseLineAllocation: Record \"Purchase Line - Alloc. Acc.\";\n+ begin\n+ // Create distribution entries\n+ PurchaseLineAllocation.Init();\n+ PurchaseLineAllocation.\"Document Type\" := PurchaseLine.\"Document Type\";\n+ PurchaseLineAllocation.\"Document No.\" := PurchaseLine.\"Document No.\";\n+ PurchaseLineAllocation.\"Line No.\" := PurchaseLine.\"Line No.\";\n+ PurchaseLineAllocation.\"Allocation Account No.\" := AllocationAccount.\"No.\";\n+ PurchaseLineAllocation.Amount := PurchaseLine.\"Line Amount\";\n+ PurchaseLineAllocation.Insert();\n+ end;\n+\n+ local procedure UpdateLineDistributionStatus(var PurchaseLine: Record \"Purchase Line\")\n+ begin\n+ PurchaseLine.\"Alloc. Acc. Distribution Date\" := Today();\n+ PurchaseLine.\"Distribution Complete\" := true;\n+ PurchaseLine.Modify();\n+ end;\n+\n+ var\n+ AllocAccNotExistErr: Label 'Allocation account %1 does not exist.', Comment = '%1 = allocation account number';\n+}\ndiff --git a/src/SKUMgt.Codeunit.al b/src/SKUMgt.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SKUMgt.Codeunit.al\n@@ -0,0 +1,67 @@\n+codeunit 50171 \"SKU Management\"\n+{\n+ Access = Internal;\n+\n+ procedure CheckSKUCreationPolicy(LocationCode: Code[10]): Boolean\n+ var\n+ Location: Record Location;\n+ begin\n+ if LocationCode = '' then\n+ exit(false);\n+\n+ Location.Get(LocationCode);\n+ Location.TestField(\"SKU Creation Policy\");\n+\n+ exit(Location.\"SKU Creation Policy\" <> Location.\"SKU Creation Policy\"::Never);\n+ end;\n+\n+ procedure CreateSKUForItem(ItemNo: Code[20]; LocationCode: Code[10])\n+ var\n+ Item: Record Item;\n+ StockkeepingUnit: Record \"Stockkeeping Unit\";\n+ begin\n+ if (ItemNo = '') or (LocationCode = '') then\n+ exit;\n+\n+ if not CheckSKUCreationPolicy(LocationCode) then\n+ Error(SKUNotAllowedErr, LocationCode);\n+\n+ Item.SetLoadFields(Description, \"Unit Cost\", \"Standard Cost\");\n+ Item.Get(ItemNo);\n+\n+ if StockkeepingUnit.Get(LocationCode, ItemNo, '') then\n+ exit;\n+\n+ StockkeepingUnit.Init();\n+ StockkeepingUnit.\"Location Code\" := LocationCode;\n+ StockkeepingUnit.\"Item No.\" := ItemNo;\n+ StockkeepingUnit.\"Variant Code\" := '';\n+ StockkeepingUnit.Description := Item.Description;\n+ StockkeepingUnit.\"Unit Cost\" := Item.\"Unit Cost\";\n+ StockkeepingUnit.\"Standard Cost\" := Item.\"Standard Cost\";\n+ StockkeepingUnit.Insert();\n+\n+ LogSKUCreation(ItemNo, LocationCode);\n+ end;\n+\n+ procedure GetSKUPolicy(LocationCode: Code[10]): Integer\n+ var\n+ Location: Record Location;\n+ begin\n+ if LocationCode = '' then\n+ exit(0);\n+\n+ Location.SetLoadFields(\"SKU Creation Policy\");\n+ if Location.Get(LocationCode) then\n+ exit(Location.\"SKU Creation Policy\".AsInteger());\n+ exit(0);\n+ end;\n+\n+ local procedure LogSKUCreation(ItemNo: Code[20]; LocationCode: Code[10])\n+ begin\n+ // Log SKU creation for audit purposes\n+ end;\n+\n+ var\n+ SKUNotAllowedErr: Label 'SKU creation is not allowed for location %1', Comment = '%1 = location code';\n+}\n", "expected_comments": [{"file": "src/AssemblyLineMgt.Codeunit.al", "line_start": 9, "line_end": 9, "body": "Redundant Get in OnAfterGetRecord context — See agent review for details.", "severity": "medium", "domain": "performance"}, {"file": "src/PurchAllocAccMgt.Codeunit.al", "line_start": 10, "line_end": 10, "body": "Get() before guard condition — See agent review for details.", "severity": "medium", "domain": "performance"}, {"file": "src/SKUMgt.Codeunit.al", "line_start": 12, "line_end": 12, "body": "Get() without SetLoadFields for one field — See agent review for details.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: record_loading (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-023", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/ReportBuilder.Codeunit.al b/src/ReportBuilder.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ReportBuilder.Codeunit.al\n@@ -0,0 +1,70 @@\n+codeunit 50401 \"Report Builder\"\n+{\n+ Access = Internal;\n+\n+ procedure BuildCsvExport(CustomerNo: Code[20]): Text\n+ var\n+ SalesLine: Record \"Sales Line\";\n+ Result: Text;\n+ begin\n+ SalesLine.SetRange(\"Sell-to Customer No.\", CustomerNo);\n+ SalesLine.SetRange(\"Document Type\", SalesLine.\"Document Type\"::Order);\n+ SalesLine.SetLoadFields(\"No.\", Quantity, \"Line Amount\");\n+ Result := CsvHeaderTok;\n+ if SalesLine.FindSet() then\n+ repeat\n+ Result += Format(SalesLine.\"No.\") + ',' +\n+ Format(SalesLine.Quantity) + ',' +\n+ Format(SalesLine.\"Line Amount\");\n+ until SalesLine.Next() = 0;\n+ exit(Result);\n+ end;\n+\n+ procedure BuildVendorReport(VendorNo: Code[20]): Text\n+ var\n+ VendorLedgerEntry: Record \"Vendor Ledger Entry\";\n+ ReportBody: Text;\n+ begin\n+ ReportBody := VendorReportHeaderTok;\n+ VendorLedgerEntry.SetRange(\"Vendor No.\", VendorNo);\n+ VendorLedgerEntry.SetRange(Open, true);\n+ if VendorLedgerEntry.FindSet() then\n+ repeat\n+ VendorLedgerEntry.CalcFields(\"Remaining Amount\");\n+ ReportBody += Format(VendorLedgerEntry.\"Entry No.\") + ' | ' +\n+ Format(VendorLedgerEntry.\"Remaining Amount\") + RowSeparatorTok;\n+ until VendorLedgerEntry.Next() = 0;\n+ exit(ReportBody);\n+ end;\n+\n+ procedure BuildItemSummary(LocationCode: Code[10]): Text\n+ var\n+ ItemLedgerEntry: Record \"Item Ledger Entry\";\n+ Summary: TextBuilder;\n+ begin\n+ Summary.Append(StrSubstNo(LocationSummaryLbl, LocationCode));\n+ Summary.AppendLine();\n+ Summary.Append(ItemSummaryHeaderTok);\n+ Summary.AppendLine();\n+ ItemLedgerEntry.SetRange(\"Location Code\", LocationCode);\n+ ItemLedgerEntry.SetRange(\"Entry Type\", ItemLedgerEntry.\"Entry Type\"::Purchase);\n+ ItemLedgerEntry.SetLoadFields(\"Item No.\", Description, Quantity);\n+ if ItemLedgerEntry.FindSet() then\n+ repeat\n+ Summary.Append(ItemLedgerEntry.\"Item No.\");\n+ Summary.Append(' | ');\n+ Summary.Append(ItemLedgerEntry.Description);\n+ Summary.Append(' | ');\n+ Summary.Append(Format(ItemLedgerEntry.Quantity));\n+ Summary.AppendLine();\n+ until ItemLedgerEntry.Next() = 0;\n+ exit(Summary.ToText());\n+ end;\n+\n+ var\n+ CsvHeaderTok: Label 'Item No.,Quantity,Amount', Locked = true;\n+ VendorReportHeaderTok: Label 'Entry No. | Remaining Amount', Locked = true;\n+ RowSeparatorTok: Label '; ', Locked = true;\n+ ItemSummaryHeaderTok: Label 'Item No. | Description | Quantity', Locked = true;\n+ LocationSummaryLbl: Label 'Location: %1', Comment = '%1 = location code';\n+}\n", "expected_comments": [{"file": "src/ReportBuilder.Codeunit.al", "line_start": 16, "line_end": 16, "body": "String concatenation with += inside a FindSet loop building CSV output. Each += allocates a new string, resulting in O(n²) performance for large record sets. — Use TextBuilder to accumulate the output string. TextBuilder.Append() is O(1) amortized and avoids repeated memory allocation.", "severity": "medium", "domain": "performance"}, {"file": "src/ReportBuilder.Codeunit.al", "line_start": 34, "line_end": 34, "body": "String concatenation with += inside a repeat..until loop building HTML body. Each concatenation copies the entire accumulated string, degrading performance as the string grows. — Use TextBuilder to construct the HTML body. Replace HtmlBody += with TextBuilder.Append() calls for efficient string building in loops.", "severity": "medium", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: textbuilder (2 findings). String concatenation with += inside loops causes O(n²) memory allocations; TextBuilder should be used instead.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__performance-024", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "performance"}, "patch": "diff --git a/src/ItemMigrator.Codeunit.al b/src/ItemMigrator.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ItemMigrator.Codeunit.al\n@@ -0,0 +1,19 @@\n+codeunit 50320 \"Item Migrator\"\n+{\n+ procedure MigrateItemDescriptions()\n+ var\n+ Item: Record Item;\n+ begin\n+ Item.SetFilter(Description, '<>%1', '');\n+ if Item.FindSet(true) then\n+ repeat\n+ Item.Description := ConvertToNewFormat(Item.Description);\n+ Item.Modify(true);\n+ until Item.Next() = 0;\n+ end;\n+\n+ local procedure ConvertToNewFormat(OldDesc: Text[100]): Text[100]\n+ begin\n+ exit(OldDesc.TrimEnd());\n+ end;\n+}\ndiff --git a/src/TestDataGenerator.Codeunit.al b/src/TestDataGenerator.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/TestDataGenerator.Codeunit.al\n@@ -0,0 +1,16 @@\n+codeunit 50321 \"Test Data Generator\"\n+{\n+ procedure CreateTestEntries(Count: Integer)\n+ var\n+ ErrorMessageRegister: Record \"Error Message Register\";\n+ i: Integer;\n+ begin\n+ for i := 1 to Count do begin\n+ ErrorMessageRegister.Init();\n+ ErrorMessageRegister.\"Entry No.\" := i;\n+ ErrorMessageRegister.Description := StrSubstNo('Test entry %1', i);\n+ ErrorMessageRegister.\"Created Date\" := Today;\n+ ErrorMessageRegister.Insert(true);\n+ end;\n+ end;\n+}\n", "expected_comments": [{"file": "src/ItemMigrator.Codeunit.al", "line_start": 11, "line_end": 11, "body": "Modify(true) inside loop fires OnModify triggers for each record. For bulk operations, consider Modify(false) unless triggers are required, or use bulk update operations. — ", "severity": "low", "domain": "performance"}, {"file": "src/TestDataGenerator.Codeunit.al", "line_start": 13, "line_end": 13, "body": "Insert(true) in loop fires OnInsert triggers for each record. For test data or bulk inserts, use Insert(false) unless triggers are needed. — ", "severity": "low", "domain": "performance"}], "match_line_tolerance": 2, "domain": "performance", "category": "code-review", "description": "True positive performance findings: Insert(true)/Modify(true) trigger overhead in loops", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-001", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/PostalCodeLookupService.Codeunit.al b/src/PostalCodeLookupService.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PostalCodeLookupService.Codeunit.al\n@@ -0,0 +1,50 @@\n+codeunit 50113 \"Postal Code Lookup Service\"\n+{\n+ Access = Internal;\n+\n+ var\n+ TypeHelper: Codeunit \"Type Helper\";\n+\n+ procedure LookupPostalAddress(PostalCode: Code[20]; City: Text[50]): Text[250]\n+ var\n+ HttpClient: HttpClient;\n+ HttpResponse: HttpResponseMessage;\n+ RequestUri: Text;\n+ ResponseText: Text;\n+ begin\n+ HttpClient.Timeout := 10000;\n+ RequestUri := StrSubstNo('https://postal-api.service.com/lookup?postal=%1&city=%2',\n+ TypeHelper.UrlEncode(PostalCode), TypeHelper.UrlEncode(City));\n+\n+ if not HttpClient.Get(RequestUri, HttpResponse) then\n+ exit('');\n+ if not HttpResponse.IsSuccessStatusCode() then\n+ exit('');\n+\n+ HttpResponse.Content.ReadAs(ResponseText);\n+ exit(ParseAddressResponse(ResponseText));\n+ end;\n+\n+ local procedure ParseAddressResponse(JsonResponse: Text): Text[250]\n+ var\n+ JsonObject: JsonObject;\n+ AddressToken: JsonToken;\n+ begin\n+ if JsonObject.ReadFrom(JsonResponse) then\n+ if JsonObject.Get('standardized_address', AddressToken) then\n+ exit(CopyStr(AddressToken.AsValue().AsText(), 1, 250));\n+ exit('');\n+ end;\n+\n+ procedure ValidateBusinessAddress(var BusinessAddress: Record \"Business Address\")\n+ var\n+ StandardizedAddress: Text[250];\n+ begin\n+ StandardizedAddress := LookupPostalAddress(BusinessAddress.\"Postal Code\", BusinessAddress.City);\n+ if StandardizedAddress <> '' then begin\n+ BusinessAddress.\"Validated Address\" := StandardizedAddress;\n+ BusinessAddress.\"Validation Status\" := BusinessAddress.\"Validation Status\"::Validated;\n+ BusinessAddress.Modify(true);\n+ end;\n+ end;\n+}\ndiff --git a/src/CustomerPiiBuffer.Table.al b/src/CustomerPiiBuffer.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerPiiBuffer.Table.al\n@@ -0,0 +1,15 @@\n+table 50321 \"Customer PII Buffer\"\n+{\n+ fields\n+ {\n+ field(1; \"Entry No.\"; Integer) { }\n+ field(10; Email; Text[80])\n+ {\n+ DataClassification = SystemMetadata;\n+ }\n+ }\n+ keys\n+ {\n+ key(PK; \"Entry No.\") { Clustered = true; }\n+ }\n+}\n", "expected_comments": [{"file": "src/CustomerPiiBuffer.Table.al", "line_start": 8, "line_end": 8, "severity": "high", "domain": "privacy", "body": "Email contains personally identifiable customer data but is classified as SystemMetadata. Use DataClassification = EndUserIdentifiableInformation or CustomerContent."}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "Address/postcode data classification (4 false positives). Agent flags address fields or postcode lookup data as incorrectly classified. Reviewers reject because: (1) address data in lookup tables is reference data not PII, (2) country/region codes are not personally identifiable, (3) the classification is appropriate for the context.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-002", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/SystemConfigurationLog.Table.al b/src/SystemConfigurationLog.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SystemConfigurationLog.Table.al\n@@ -0,0 +1,32 @@\n+table 50111 \"System Configuration Log\"\n+{\n+ Caption = 'System Configuration Log';\n+ DataClassification = SystemMetadata;\n+ TableType = Temporary;\n+\n+ fields\n+ {\n+ field(1; \"Entry No.\"; Integer)\n+ {\n+ Caption = 'Entry No.';\n+ AutoIncrement = true;\n+ }\n+ field(2; \"Configuration Area\"; Text[50])\n+ {\n+ Caption = 'Configuration Area';\n+ }\n+ field(3; \"Parameter Name\"; Text[100])\n+ {\n+ Caption = 'Parameter Name';\n+ }\n+ field(4; \"Parameter Value\"; Text[250])\n+ {\n+ Caption = 'Parameter Value';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Entry No.\") { Clustered = true; }\n+ }\n+}\ndiff --git a/src/ContactConsentCache.Table.al b/src/ContactConsentCache.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ContactConsentCache.Table.al\n@@ -0,0 +1,15 @@\n+table 50322 \"Contact Consent Cache\"\n+{\n+ fields\n+ {\n+ field(1; \"Entry No.\"; Integer) { }\n+ field(10; \"Phone No.\"; Text[30])\n+ {\n+ DataClassification = ToBeClassified;\n+ }\n+ }\n+ keys\n+ {\n+ key(PK; \"Entry No.\") { Clustered = true; }\n+ }\n+}\n", "expected_comments": [{"file": "src/ContactConsentCache.Table.al", "line_start": 8, "line_end": 8, "severity": "medium", "domain": "privacy", "body": "ToBeClassified is only for development and must be resolved before release, especially for a phone number field. Set an explicit CustomerContent or EndUserIdentifiableInformation classification."}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "Missing DataClassification on table fields (59 false positives). Agent flags table fields missing explicit DataClassification property. Reviewers reject because: (1) table-level DataClassification covers all fields, (2) fields contain system/business data not PII, (3) fields are in temporary tables, or (4) the classification is inherited.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-003", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/EAOutboxEmail.Table.al b/src/EAOutboxEmail.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/EAOutboxEmail.Table.al\n@@ -0,0 +1,33 @@\n+table 50130 \"EA Outbox Email\"\n+{\n+ Caption = 'Expense Agent Outbox Email';\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"Entry No.\"; Integer)\n+ {\n+ Caption = 'Entry No.';\n+ AutoIncrement = true;\n+ DataClassification = SystemMetadata;\n+ }\n+ field(10; \"From Address\"; Text[250])\n+ {\n+ Caption = 'From Address';\n+ DataClassification = EndUserIdentifiableInformation;\n+ }\n+ field(20; Subject; Text[250])\n+ {\n+ Caption = 'Subject';\n+ DataClassification = CustomerContent;\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Entry No.\")\n+ {\n+ Clustered = true;\n+ }\n+ }\n+}\ndiff --git a/src/OutlookIntegrationHelper.Codeunit.al b/src/OutlookIntegrationHelper.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/OutlookIntegrationHelper.Codeunit.al\n@@ -0,0 +1,14 @@\n+codeunit 50131 \"Outlook Integration Helper\"\n+{\n+ Access = Internal;\n+\n+ var\n+ GraphNotificationTok: Label 'MicrosoftGraphNotification', Locked = true;\n+\n+ procedure IsGraphNotificationConsented(): Boolean\n+ var\n+ PrivacyNotice: Codeunit \"Privacy Notice\";\n+ begin\n+ exit(PrivacyNotice.GetPrivacyNoticeApprovalState(GraphNotificationTok) = \"Privacy Notice Approval State\"::Agreed);\n+ end;\n+}\ndiff --git a/src/SOAFiltersImpl.Codeunit.al b/src/SOAFiltersImpl.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SOAFiltersImpl.Codeunit.al\n@@ -0,0 +1,9 @@\n+codeunit 50132 \"SOA Filters Impl\"\n+{\n+ Access = Internal;\n+\n+ procedure IsValidEmailFilter(EmailAddress: Text[250]): Boolean\n+ begin\n+ exit((EmailAddress <> '') and (StrPos(EmailAddress, '@') > 0));\n+ end;\n+}\ndiff --git a/src/CustomerEmailValidator.Codeunit.al b/src/CustomerEmailValidator.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerEmailValidator.Codeunit.al\n@@ -0,0 +1,16 @@\n+codeunit 50323 \"Customer Email Validator\"\n+{\n+ procedure RejectEmail(CustomerName: Text[100]; EmailAddress: Text[80])\n+ var\n+ InvalidEmailErr: Label 'Customer %1 has invalid email %2.';\n+ ErrorMessage: Text;\n+ begin\n+ ErrorMessage := StrSubstNo(InvalidEmailErr, CustomerName, EmailAddress);\n+ Error(ErrorMessage);\n+ end;\n+\n+ procedure Check(CustomerName: Text[100]; EmailAddress: Text[80])\n+ begin\n+ this.RejectEmail(CustomerName, EmailAddress);\n+ end;\n+}\n", "expected_comments": [{"file": "src/CustomerEmailValidator.Codeunit.al", "line_start": 8, "line_end": 9, "severity": "high", "domain": "privacy", "body": "PII (customer name and email) is pre-built into a Text variable via StrSubstNo and then passed to Error. The resulting message is logged to telemetry with the PII inlined. Avoid pre-baking customer data into error messages; surface generic errors and report PII through a privacy-compliant channel."}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "Email addresses in API/system calls (3 false positives). Agent flags email addresses used in Graph API calls or email processing. Reviewers reject because: (1) email is required for the feature to function, (2) the API call is to Microsoft services, (3) proper consent/privacy controls exist.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-004", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/SystemErrorHandler.Codeunit.al b/src/SystemErrorHandler.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SystemErrorHandler.Codeunit.al\n@@ -0,0 +1,43 @@\n+codeunit 50115 SystemErrorHandler\n+{\n+ var\n+ ErrorLoggedMsg: Label 'Error logged with reference: %1', Comment = '%1 = System ID';\n+ ValidationFailedMsg: Label 'Validation failed for record %1 in field %2', Comment = '%1 = Record ID, %2 = Field name';\n+\n+ procedure HandleSystemError(ErrorContext: Text[100]; SystemId: Guid; ErrorDetails: Text[500])\n+ var\n+ ErrorLogEntry: Record \"Error Log Entry\";\n+ begin\n+\n+ ErrorLogEntry.Init();\n+ ErrorLogEntry.\"Entry No.\" := GetNextEntryNo();\n+ ErrorLogEntry.\"Error Context\" := ErrorContext;\n+ ErrorLogEntry.\"System Reference\" := SystemId;\n+ ErrorLogEntry.\"Error Message\" := ErrorDetails;\n+ ErrorLogEntry.\"Date Time\" := CurrentDateTime;\n+ ErrorLogEntry.Insert();\n+\n+ Message(ErrorLoggedMsg, SystemId);\n+ end;\n+\n+ procedure ProcessSystemValidationError(RecordId: RecordId; ValidationField: Text[50])\n+ var\n+ ErrorMessage: Text[500];\n+ begin\n+\n+ ErrorMessage := StrSubstNo(ValidationFailedMsg, Format(RecordId), ValidationField);\n+\n+ HandleSystemError('VALIDATION', RecordId.SystemId, ErrorMessage);\n+\n+ end;\n+\n+ local procedure GetNextEntryNo(): Integer\n+ var\n+ ErrorLogEntry: Record \"Error Log Entry\";\n+ begin\n+ ErrorLogEntry.LockTable();\n+ if ErrorLogEntry.FindLast() then\n+ exit(ErrorLogEntry.\"Entry No.\" + 1);\n+ exit(1);\n+ end;\n+}\ndiff --git a/src/ErrorLogEntry.Table.al b/src/ErrorLogEntry.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ErrorLogEntry.Table.al\n@@ -0,0 +1,42 @@\n+table 50116 \"Error Log Entry\"\n+{\n+ Caption = 'Error Log Entry';\n+ DataClassification = SystemMetadata;\n+\n+ fields\n+ {\n+ field(1; \"Entry No.\"; Integer)\n+ {\n+ Caption = 'Entry No.';\n+ DataClassification = SystemMetadata;\n+ }\n+ field(2; \"Error Context\"; Enum \"Error Context Type\")\n+ {\n+ Caption = 'Error Context';\n+ DataClassification = SystemMetadata;\n+ }\n+ field(3; \"System Reference\"; Guid)\n+ {\n+ Caption = 'System Reference';\n+ DataClassification = SystemMetadata;\n+ }\n+ field(4; \"Error Message\"; Text[500])\n+ {\n+ Caption = 'Error Message';\n+ DataClassification = CustomerContent;\n+ }\n+ field(5; \"Date Time\"; DateTime)\n+ {\n+ Caption = 'Date Time';\n+ DataClassification = SystemMetadata;\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Entry No.\")\n+ {\n+ Clustered = true;\n+ }\n+ }\n+}\ndiff --git a/src/AttachmentErrorReporter.Codeunit.al b/src/AttachmentErrorReporter.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/AttachmentErrorReporter.Codeunit.al\n@@ -0,0 +1,15 @@\n+codeunit 50324 \"Attachment Error Reporter\"\n+{\n+ procedure RaiseAttachmentFailure()\n+ var\n+ ErrorMessage: Text;\n+ begin\n+ ErrorMessage := StrSubstNo('Attachment failed: %1', GetLastErrorText(true));\n+ Error(ErrorMessage);\n+ end;\n+\n+ procedure ReportLastFailure()\n+ begin\n+ this.RaiseAttachmentFailure();\n+ end;\n+}\n", "expected_comments": [{"file": "src/AttachmentErrorReporter.Codeunit.al", "line_start": 7, "line_end": 7, "severity": "high", "domain": "privacy", "body": "GetLastErrorText can contain customer content such as filenames or record values, and StrSubstNo bakes it into an Error string that telemetry logs verbatim. Use a generic Error message instead."}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "PII in error messages (8 false positives). Agent flags GUIDs, document IDs, or system IDs in error messages as PII. Reviewers reject because: (1) GUIDs/SystemIds are not personally identifiable, (2) document IDs are business data, (3) error context is needed for troubleshooting.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-005", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/BusinessIntegrationEvents.Codeunit.al b/src/BusinessIntegrationEvents.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/BusinessIntegrationEvents.Codeunit.al\n@@ -0,0 +1,38 @@\n+codeunit 50117 \"Business Integration Events\"\n+{\n+ Access = Internal;\n+\n+ [IntegrationEvent(true, false)]\n+ local procedure OnBeforeProcessBusinessEntity(var BusinessEntity: Record \"Business Entity\"; var IsHandled: Boolean)\n+ begin\n+ end;\n+\n+ procedure ProcessBusinessEntityBatch(var TempBusinessEntity: Record \"Business Entity\" temporary)\n+ var\n+ IsHandled: Boolean;\n+ ProcessedCount: Integer;\n+ begin\n+ if TempBusinessEntity.FindSet() then\n+ repeat\n+ IsHandled := false;\n+ OnBeforeProcessBusinessEntity(TempBusinessEntity, IsHandled);\n+ if not IsHandled then begin\n+ ProcessSingleBusinessEntity(TempBusinessEntity);\n+ ProcessedCount += 1;\n+ end;\n+ OnAfterBusinessEntityProcessed(TempBusinessEntity.\"Entity No.\", ProcessedCount);\n+ until TempBusinessEntity.Next() = 0;\n+ end;\n+\n+ [IntegrationEvent(true, false)]\n+ local procedure OnAfterBusinessEntityProcessed(EntityNo: Code[20]; ProcessedCount: Integer)\n+ begin\n+ end;\n+\n+ local procedure ProcessSingleBusinessEntity(var BusinessEntity: Record \"Business Entity\")\n+ begin\n+ BusinessEntity.\"Processing Status\" := BusinessEntity.\"Processing Status\"::Processed;\n+ BusinessEntity.\"Processed Date\" := Today;\n+ BusinessEntity.Modify(true);\n+ end;\n+}\ndiff --git a/src/CustomerTelemetryLogger.Codeunit.al b/src/CustomerTelemetryLogger.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerTelemetryLogger.Codeunit.al\n@@ -0,0 +1,17 @@\n+codeunit 50325 \"Customer Telemetry Logger\"\n+{\n+ procedure LogCustomerProcessed(CustomerName: Text[100])\n+ var\n+ TraceCategory: Text[30];\n+ begin\n+ TraceCategory := 'Privacy';\n+ Session.LogMessage('0000P01', StrSubstNo('Processed customer %1', CustomerName),\n+ Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::All,\n+ 'Category', TraceCategory);\n+ end;\n+\n+ procedure LogDefault(CustomerName: Text[100])\n+ begin\n+ this.LogCustomerProcessed(CustomerName);\n+ end;\n+}\n", "expected_comments": [{"file": "src/CustomerTelemetryLogger.Codeunit.al", "line_start": 8, "line_end": 8, "severity": "high", "domain": "privacy", "body": "The telemetry message includes a customer name, which is PII and will be sent to telemetry. Log a generic message and keep customer data out of Session.LogMessage text."}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "Integration event parameter exposure (3 false positives). Agent flags integration events that pass record parameters (e.g., CustLedgerEntry, VendorLedgerEntry) as exposing PII. Reviewers reject because: (1) integration events are internal APIs, (2) consuming code already has table permissions, (3) this is standard BC event pattern.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-006", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/BusinessSystemLogger.Codeunit.al b/src/BusinessSystemLogger.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/BusinessSystemLogger.Codeunit.al\n@@ -0,0 +1,45 @@\n+codeunit 50112 BusinessSystemLogger\n+{\n+ procedure LogVendorProcessing(VendorCode: Code[20]; ProcessingStep: Text[100])\n+ var\n+ ActivityLog: Record \"Activity Log\";\n+ begin\n+\n+ ActivityLog.LogActivity(\n+ Database::Vendor,\n+ ActivityLog.Status::Success,\n+ 'VendorProcessing',\n+ StrSubstNo('Processing completed for Vendor: %1 at step: %2', VendorCode, ProcessingStep),\n+ '');\n+\n+ Message(VendorLoggedMsg, VendorCode);\n+ end;\n+\n+ procedure LogSystemOperation(OperationType: Text[50]; Details: Text[250])\n+ begin\n+\n+ Session.LogMessage('VendorProcess', StrSubstNo('Operation: %1 - Details: %2', OperationType, Details), Verbosity::Information,\n+ DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Vendor', '');\n+ end;\n+\n+ procedure ProcessVendorBatch(var VendorBatch: Record Vendor temporary)\n+ var\n+ ProcessedCount: Integer;\n+ VendorCode: Code[20];\n+ begin\n+\n+ if VendorBatch.FindSet() then\n+ repeat\n+ VendorCode := VendorBatch.\"No.\";\n+\n+ LogSystemOperation('BATCH_PROCESSING', StrSubstNo('Vendor %1 processed in batch', VendorCode));\n+ ProcessedCount += 1;\n+ until VendorBatch.Next() = 0;\n+\n+ Message(BatchCompletedMsg, ProcessedCount);\n+ end;\n+\n+ var\n+ VendorLoggedMsg: Label 'Vendor %1 processing logged successfully', Comment = '%1 = Vendor Code';\n+ BatchCompletedMsg: Label 'Batch processing completed: %1 vendors processed', Comment = '%1 = vendor count';\n+}\n", "expected_comments": [{"file": "src/BusinessSystemLogger.Codeunit.al", "line_start": 18, "line_end": 18, "domain": "privacy", "body": "LogSystemOperation embeds a caller-supplied free-form Details (Text[250]) directly into the Session.LogMessage telemetry text while classifying the payload as SystemMetadata. Callers can pass CustomerContent/EUII, so PII can be logged to telemetry under the wrong classification — Log a fixed non-personal message and pass vetted values as custom dimensions, or declare a stronger DataClassification", "severity": "high"}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "PII in log/telemetry messages (13 false positives). Agent flags vendor IDs, document numbers, or error stacks in log messages as PII exposure. Reviewers reject because: (1) vendor IDs are business identifiers not personal data, (2) telemetry uses SystemMetadata classification, (3) error stacks are necessary for debugging.", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-007", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/CustomerContactBuffer.Table.al b/src/CustomerContactBuffer.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerContactBuffer.Table.al\n@@ -0,0 +1,31 @@\n+table 50118 \"Customer Contact Buffer\"\n+{\n+ Caption = 'Customer Contact Buffer';\n+ DataClassification = CustomerContent;\n+ TableType = Temporary;\n+\n+ fields\n+ {\n+ field(1; \"Customer Name\"; Text[100])\n+ {\n+ Caption = 'Customer Name';\n+ }\n+ field(2; \"Contact Email\"; Text[80])\n+ {\n+ Caption = 'Contact Email';\n+ }\n+ field(3; \"Phone No.\"; Text[30])\n+ {\n+ Caption = 'Phone No.';\n+ }\n+ field(4; \"Billing Address\"; Text[100])\n+ {\n+ Caption = 'Billing Address';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Customer Name\") { Clustered = true; }\n+ }\n+}\ndiff --git a/src/ExpenseTelemetryLogger.Codeunit.al b/src/ExpenseTelemetryLogger.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExpenseTelemetryLogger.Codeunit.al\n@@ -0,0 +1,16 @@\n+codeunit 50326 \"Expense Telemetry Logger\"\n+{\n+ procedure LogExpenseRelease(EmployeeNo: Code[20])\n+ var\n+ FeatureTelemetry: Codeunit \"Feature Telemetry\";\n+ CustomDimensions: Dictionary of [Text, Text];\n+ begin\n+ CustomDimensions.Add('EmployeeNo', EmployeeNo);\n+ FeatureTelemetry.LogUsage('0000P02', 'Expense Review', 'Document Released', CustomDimensions);\n+ end;\n+\n+ procedure RecordRelease(EmployeeNo: Code[20])\n+ begin\n+ this.LogExpenseRelease(EmployeeNo);\n+ end;\n+}\n", "expected_comments": [{"file": "src/ExpenseTelemetryLogger.Codeunit.al", "line_start": 8, "line_end": 8, "severity": "high", "domain": "privacy", "body": "Employee numbers can identify individuals and must not be sent in FeatureTelemetry custom dimensions. Remove the EmployeeNo dimension or replace it with a non-identifying aggregate."}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "Permission set / blanket classification (1 false positives). Agent flags blanket DataClassification changes or permission set exposure. Reviewers reject because: (1) the classification approach is intentional, (2) permission sets are system metadata.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-008", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/BusinessEntityRegistry.Table.al b/src/BusinessEntityRegistry.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/BusinessEntityRegistry.Table.al\n@@ -0,0 +1,34 @@\n+table 50110 \"Business Entity Registry\"\n+{\n+ DataClassification = CustomerContent; // Table-level classification covers all fields\n+ Caption = 'Business Entity Registry';\n+\n+ fields\n+ {\n+ field(1; \"Entity ID\"; Code[20])\n+ {\n+ Caption = 'Entity ID';\n+ }\n+ field(2; \"Company Name\"; Text[100])\n+ {\n+ Caption = 'Company Name';\n+ }\n+ field(3; \"Business Address\"; Text[250])\n+ {\n+ Caption = 'Business Address';\n+ }\n+ field(4; \"Registration Number\"; Text[50])\n+ {\n+ Caption = 'Registration Number';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Entity ID\")\n+ {\n+ Clustered = true;\n+ }\n+ }\n+\n+}\ndiff --git a/src/CustomerSyncDispatcher.Codeunit.al b/src/CustomerSyncDispatcher.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerSyncDispatcher.Codeunit.al\n@@ -0,0 +1,22 @@\n+codeunit 50327 \"Customer Sync Dispatcher\"\n+{\n+ procedure SendCustomer(Customer: Record Customer)\n+ var\n+ HttpClient: HttpClient;\n+ HttpContent: HttpContent;\n+ HttpResponse: HttpResponseMessage;\n+ begin\n+ HttpContent.WriteFrom(this.BuildPayload(Customer));\n+ HttpClient.Post('https://api.contoso.example/customers', HttpContent, HttpResponse);\n+ end;\n+\n+ local procedure BuildPayload(Customer: Record Customer): Text\n+ var\n+ PayloadJson: JsonObject;\n+ PayloadText: Text;\n+ begin\n+ PayloadJson.Add('email', Customer.\"E-Mail\");\n+ PayloadJson.WriteTo(PayloadText);\n+ exit(PayloadText);\n+ end;\n+}\n", "expected_comments": [{"file": "src/CustomerSyncDispatcher.Codeunit.al", "line_start": 10, "line_end": 10, "severity": "high", "domain": "privacy", "body": "This outgoing HTTP request sends customer data to an external service without a Privacy Notice consent check in the code path. Verify consent with PrivacyNotice.GetPrivacyNoticeApprovalState before posting."}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "PII in table fields (names, addresses) (3 false positives). Agent flags fields containing names or addresses as missing PII classification. Reviewers reject because: (1) the table already has appropriate DataClassification, (2) these are business entity names not personal names, (3) migration tables have different privacy requirements.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-009", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/TaxDataMigrationHelper.Codeunit.al b/src/TaxDataMigrationHelper.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/TaxDataMigrationHelper.Codeunit.al\n@@ -0,0 +1,34 @@\n+codeunit 50116 \"Tax Data Migration Helper\"\n+{\n+ Access = Internal;\n+\n+ var\n+ ValidationContextTxt: Label 'Tax ID validation during migration', Locked = true;\n+ ProgressTxt: Label 'Tax data migration progress: %1 of %2 records processed', Locked = true;\n+\n+ procedure MigrateTaxInformation(var VendorRecord: Record Vendor; SourceTaxId: Text[50])\n+ begin\n+ if SourceTaxId <> '' then begin\n+ VendorRecord.Validate(\"Federal ID No.\", FormatTaxId(SourceTaxId));\n+ VendorRecord.Modify(true);\n+ end;\n+ end;\n+\n+ procedure ValidateTaxDataIntegrity(ExpectedTaxId: Text[50]; ActualTaxId: Text[50]): Boolean\n+ var\n+ ValidationAssert: Codeunit \"Migration Validation Assert\";\n+ begin\n+ exit(ValidationAssert.ValidateAreEqual(ExpectedTaxId, ActualTaxId, true, ValidationContextTxt));\n+ end;\n+\n+ local procedure FormatTaxId(RawTaxId: Text[50]): Text[50]\n+ begin\n+ exit(DelChr(RawTaxId, '=', '-()., '));\n+ end;\n+\n+ procedure LogMigrationProgress(TotalRecords: Integer; ProcessedRecords: Integer)\n+ begin\n+ Session.LogMessage('TaxMigration', StrSubstNo(ProgressTxt, ProcessedRecords, TotalRecords),\n+ Verbosity::Information, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher);\n+ end;\n+}\ndiff --git a/src/EmployeeIdentityStaging.Table.al b/src/EmployeeIdentityStaging.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/EmployeeIdentityStaging.Table.al\n@@ -0,0 +1,15 @@\n+table 50328 \"Employee Identity Staging\"\n+{\n+ fields\n+ {\n+ field(1; \"Entry No.\"; Integer) { }\n+ field(10; \"Social Security No.\"; Text[30])\n+ {\n+ Caption = 'Social Security No.';\n+ }\n+ }\n+ keys\n+ {\n+ key(PK; \"Entry No.\") { Clustered = true; }\n+ }\n+}\n", "expected_comments": [{"file": "src/EmployeeIdentityStaging.Table.al", "line_start": 6, "line_end": 6, "severity": "high", "domain": "privacy", "body": "Social Security No. stores sensitive PII but the field has no DataClassification. Add EndUserIdentifiableInformation or CustomerContent classification on the field or table."}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "Tax ID (TIN) handling in migration code (4 false positives). Agent flags TIN/federal ID processing in data migration codeunits as PII risk. Reviewers reject because: (1) migration code necessarily processes this data, (2) data is already classified at the table level, (3) migration is a controlled process.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-010", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/ExternalCRMSync.Codeunit.al b/src/ExternalCRMSync.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExternalCRMSync.Codeunit.al\n@@ -0,0 +1,46 @@\n+codeunit 57300 \"External CRM Sync\"\n+{\n+ Access = Public;\n+\n+ procedure SyncCustomerToExternalCRM(Customer: Record Customer)\n+ var\n+ HttpClient: HttpClient;\n+ HttpContent: HttpContent;\n+ HttpResponse: HttpResponseMessage;\n+ JsonPayload: Text;\n+ begin\n+ if Customer.\"E-Mail\" = '' then\n+ exit;\n+\n+ JsonPayload := StrSubstNo(\n+ '{\"email\":\"%1\",\"name\":\"%2\",\"phone\":\"%3\",\"address\":\"%4\"}',\n+ Customer.\"E-Mail\",\n+ Customer.Name,\n+ Customer.\"Phone No.\",\n+ Customer.Address);\n+\n+ HttpContent.WriteFrom(JsonPayload);\n+ HttpContent.GetHeaders().Clear();\n+ HttpContent.GetHeaders().Add('Content-Type', 'application/json');\n+\n+ // Sends customer data to external service without privacy consent check\n+ HttpClient.Post('https://api.externalcrm.com/contacts/sync', HttpContent, HttpResponse);\n+\n+ if not HttpResponse.IsSuccessStatusCode() then\n+ Error('Failed to sync customer %1 to external CRM', Customer.\"No.\");\n+ end;\n+\n+ procedure SyncAllPendingCustomers()\n+ var\n+ Customer: Record Customer;\n+ begin\n+ Customer.SetRange(\"CRM Sync Required\", true);\n+ if Customer.FindSet() then\n+ repeat\n+ SyncCustomerToExternalCRM(Customer);\n+ Customer.\"CRM Sync Required\" := false;\n+ Customer.Modify(false);\n+ until Customer.Next() = 0;\n+ end;\n+}\n+\ndiff --git a/src/OutboxEmailDispatcher.Codeunit.al b/src/OutboxEmailDispatcher.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/OutboxEmailDispatcher.Codeunit.al\n@@ -0,0 +1,52 @@\n+codeunit 57301 \"Outbox Email Dispatcher\"\n+{\n+ Access = Public;\n+\n+ procedure SendPendingEmails()\n+ var\n+ OutboxEmail: Record \"EA Outbox Email\";\n+ HttpClient: HttpClient;\n+ HttpContent: HttpContent;\n+ HttpResponse: HttpResponseMessage;\n+ GraphUrl: Text;\n+ JsonPayload: Text;\n+ begin\n+ OutboxEmail.SetRange(\"Send Status\", OutboxEmail.\"Send Status\"::Pending);\n+ if OutboxEmail.FindSet(true) then\n+ repeat\n+ GraphUrl := 'https://graph.microsoft.com/v1.0/me/sendMail';\n+\n+ JsonPayload := BuildMailPayload(OutboxEmail);\n+\n+ HttpContent.WriteFrom(JsonPayload);\n+ HttpContent.GetHeaders().Clear();\n+ HttpContent.GetHeaders().Add('Content-Type', 'application/json');\n+\n+ // Sends email via Microsoft Graph without checking Privacy Notice consent\n+ if HttpClient.Post(GraphUrl, HttpContent, HttpResponse) then begin\n+ if HttpResponse.IsSuccessStatusCode() then begin\n+ OutboxEmail.\"Send Status\" := OutboxEmail.\"Send Status\"::Sent;\n+ OutboxEmail.\"Sent DateTime\" := CurrentDateTime;\n+ end else begin\n+ OutboxEmail.\"Send Status\" := OutboxEmail.\"Send Status\"::Failed;\n+ OutboxEmail.\"Retry Count\" += 1;\n+ end;\n+ OutboxEmail.Modify(false);\n+ end;\n+ until OutboxEmail.Next() = 0;\n+ end;\n+\n+ local procedure BuildMailPayload(OutboxEmail: Record \"EA Outbox Email\"): Text\n+ var\n+ JsonPayload: Text;\n+ begin\n+ JsonPayload := StrSubstNo(\n+ '{\"message\":{\"subject\":\"%1\",\"toRecipients\":[{\"emailAddress\":{\"address\":\"%2\"}}],' +\n+ '\"body\":{\"contentType\":\"HTML\",\"content\":\"%3\"}},\"saveToSentItems\":true}',\n+ OutboxEmail.Subject,\n+ OutboxEmail.\"To Line\",\n+ OutboxEmail.GetBodyText());\n+ exit(JsonPayload);\n+ end;\n+}\n+\n", "expected_comments": [{"file": "src/ExternalCRMSync.Codeunit.al", "line_start": 5, "line_end": 5, "domain": "privacy", "body": "Customer PII fields (Name, E-Mail, Phone No., Address) are transmitted externally with no DataClassification consideration for EndUserIdentifiableInformation/EndUserPseudonymousIdentifiers — Classify the transmitted personal data and tag the PII flow so it is traceable for GDPR records of processing", "severity": "high"}, {"file": "src/ExternalCRMSync.Codeunit.al", "line_start": 27, "line_end": 27, "body": "HttpClient.Post sends customer data (email, name, phone, address) to external CRM service without PrivacyNotice.GetPrivacyNoticeApprovalState() check in the code path — Add Privacy Notice consent verification before sending customer data externally", "severity": "medium", "domain": "privacy"}, {"file": "src/OutboxEmailDispatcher.Codeunit.al", "line_start": 26, "line_end": 26, "body": "HttpClient.Post sends email data via Microsoft Graph API without PrivacyNotice.GetPrivacyNoticeApprovalState() check for Exchange integration consent — Verify Privacy Notice consent for Exchange/Graph integration before sending emails", "severity": "medium", "domain": "privacy"}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "True positive privacy findings: outgoing requests sending customer data to external services without Privacy Notice consent verification in the code path", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-011", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/ContactSyncFolder.Table.al b/src/ContactSyncFolder.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ContactSyncFolder.Table.al\n@@ -0,0 +1,29 @@\n+table 50150 \"Contact Sync Folder\"\n+{\n+ Caption = 'Contact Sync Folder';\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"Folder ID\"; Code[20])\n+ {\n+ Caption = 'Folder ID';\n+ DataClassification = SystemMetadata;\n+ }\n+ field(2; \"Folder Name\"; Text[100])\n+ {\n+ Caption = 'Folder Name';\n+ DataClassification = CustomerContent;\n+ }\n+ field(3; \"Contact Notes\"; Text[2048])\n+ {\n+ Caption = 'Contact Notes';\n+ DataClassification = SystemMetadata;\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Folder ID\") { Clustered = true; }\n+ }\n+}\ndiff --git a/src/FinancialReportBuffer.Table.al b/src/FinancialReportBuffer.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/FinancialReportBuffer.Table.al\n@@ -0,0 +1,28 @@\n+table 50151 \"Financial Report Buffer\"\n+{\n+ Caption = 'Financial Report Buffer';\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"Entry No.\"; Integer)\n+ {\n+ Caption = 'Entry No.';\n+ DataClassification = SystemMetadata;\n+ }\n+ field(2; \"Account No.\"; Code[20])\n+ {\n+ Caption = 'Account No.';\n+ DataClassification = CustomerContent;\n+ }\n+ field(3; \"Category Code\"; Code[20])\n+ {\n+ Caption = 'Category Code';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Entry No.\") { Clustered = true; }\n+ }\n+}\ndiff --git a/src/O365ContactBuffer.Table.al b/src/O365ContactBuffer.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/O365ContactBuffer.Table.al\n@@ -0,0 +1,28 @@\n+table 50152 \"O365 Contact Buffer\"\n+{\n+ Caption = 'O365 Contact Buffer';\n+ DataClassification = EndUserIdentifiableInformation;\n+\n+ fields\n+ {\n+ field(1; \"Contact No.\"; Code[20])\n+ {\n+ Caption = 'Contact No.';\n+ DataClassification = SystemMetadata;\n+ }\n+ field(2; \"Name\"; Text[100])\n+ {\n+ Caption = 'Name';\n+ DataClassification = EndUserIdentifiableInformation;\n+ }\n+ field(3; \"County\"; Text[30])\n+ {\n+ Caption = 'County';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Contact No.\") { Clustered = true; }\n+ }\n+}\ndiff --git a/src/ServiceShipmentLineBuffer.Table.al b/src/ServiceShipmentLineBuffer.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ServiceShipmentLineBuffer.Table.al\n@@ -0,0 +1,28 @@\n+table 50153 \"Service Shipment Line Buffer\"\n+{\n+ Caption = 'Service Shipment Line Buffer';\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"Document No.\"; Code[20])\n+ {\n+ Caption = 'Document No.';\n+ DataClassification = CustomerContent;\n+ }\n+ field(2; \"Line No.\"; Integer)\n+ {\n+ Caption = 'Line No.';\n+ DataClassification = SystemMetadata;\n+ }\n+ field(3; \"External Document No.\"; Code[35])\n+ {\n+ Caption = 'External Document No.';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Document No.\", \"Line No.\") { Clustered = true; }\n+ }\n+}\n", "expected_comments": [{"file": "src/ContactSyncFolder.Table.al", "line_start": 18, "line_end": 18, "domain": "privacy", "severity": "medium", "body": "Field 'Contact Notes' is classified SystemMetadata but stores free-text personal notes (CustomerContent/EUII) — reclassify to CustomerContent or EndUserIdentifiableInformation."}, {"file": "src/FinancialReportBuffer.Table.al", "line_start": 18, "line_end": 18, "domain": "privacy", "severity": "medium", "body": "Field 'Category Code' is missing a DataClassification property — add an explicit DataClassification."}, {"file": "src/O365ContactBuffer.Table.al", "line_start": 18, "line_end": 18, "domain": "privacy", "severity": "medium", "body": "Field 'County' is missing a DataClassification property — add an explicit DataClassification (address data is EndUserIdentifiableInformation)."}, {"file": "src/ServiceShipmentLineBuffer.Table.al", "line_start": 18, "line_end": 18, "domain": "privacy", "severity": "medium", "body": "Field 'External Document No.' is missing a DataClassification property — add an explicit DataClassification."}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "True positive privacy findings: dataclassification (trimmed to 6 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-012", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/ExpenseUser.Table.al b/src/ExpenseUser.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExpenseUser.Table.al\n@@ -0,0 +1,43 @@\n+table 50140 \"Expense User\"\n+{\n+ Caption = 'Expense User';\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"User Security ID\"; Guid)\n+ {\n+ Caption = 'User Security ID';\n+ DataClassification = EndUserPseudonymousIdentifiers;\n+ }\n+ field(10; \"E-Mail\"; Text[250])\n+ {\n+ Caption = 'E-Mail';\n+ DataClassification = EndUserIdentifiableInformation;\n+ }\n+ field(20; \"Full Name\"; Text[100])\n+ {\n+ Caption = 'Full Name';\n+ DataClassification = EndUserIdentifiableInformation;\n+ }\n+ field(30; \"Allow Approval\"; Boolean)\n+ {\n+ Caption = 'Allow Approval';\n+ DataClassification = SystemMetadata;\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"User Security ID\") { Clustered = true; }\n+ }\n+\n+ var\n+ ApproverInvalidErr: Label 'User %1 with email %2 cannot approve expenses.', Comment = '%1 = Full Name, %2 = Email';\n+\n+ procedure ValidateApprover()\n+ begin\n+ if not \"Allow Approval\" then\n+ Error(ApproverInvalidErr, \"Full Name\", \"E-Mail\");\n+ end;\n+}\ndiff --git a/src/JobQueueErrorHandler.Codeunit.al b/src/JobQueueErrorHandler.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/JobQueueErrorHandler.Codeunit.al\n@@ -0,0 +1,11 @@\n+codeunit 50141 \"Job Queue Error Handler\"\n+{\n+ var\n+ JobFailedTxt: Label 'Job %1 failed: %2', Locked = true;\n+\n+ procedure LogJobError(JobId: Guid)\n+ begin\n+ Session.LogMessage('JQE001', StrSubstNo(JobFailedTxt, JobId, GetLastErrorText()),\n+ Verbosity::Error, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher);\n+ end;\n+}\ndiff --git a/src/ReleaseExpenseDocument.Codeunit.al b/src/ReleaseExpenseDocument.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ReleaseExpenseDocument.Codeunit.al\n@@ -0,0 +1,19 @@\n+codeunit 50142 \"Release Expense Document\"\n+{\n+ var\n+ MerchantInvalidErr: Label 'Merchant %1 is not valid for document %2.', Comment = '%1 = Merchant Name, %2 = Document No.';\n+ FeatureNameTxt: Label 'Expense Agent', Locked = true;\n+ DocReleasedTxt: Label 'Document Released', Locked = true;\n+\n+ procedure Release(var ExpenseHeader: Record \"Expense Header\")\n+ var\n+ FeatureTelemetry: Codeunit \"Feature Telemetry\";\n+ CustomDimensions: Dictionary of [Text, Text];\n+ begin\n+ if not ExpenseHeader.\"Merchant Approved\" then\n+ Error(MerchantInvalidErr, ExpenseHeader.\"Merchant Name\", ExpenseHeader.\"No.\");\n+\n+ CustomDimensions.Add('EmployeeNo', ExpenseHeader.\"Employee No.\");\n+ FeatureTelemetry.LogUsage('0000EA1', FeatureNameTxt, DocReleasedTxt, CustomDimensions);\n+ end;\n+}\ndiff --git a/src/ReleaseExpReportDocument.Codeunit.al b/src/ReleaseExpReportDocument.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ReleaseExpReportDocument.Codeunit.al\n@@ -0,0 +1,19 @@\n+codeunit 50143 \"Release Exp Report Document\"\n+{\n+ var\n+ MerchantInvalidErr: Label 'Merchant %1 is not valid for report %2.', Comment = '%1 = Merchant Name, %2 = Report No.';\n+ FeatureNameTxt: Label 'Expense Agent', Locked = true;\n+ ReportReleasedTxt: Label 'Report Released', Locked = true;\n+\n+ procedure Release(var ExpenseReportHeader: Record \"Expense Report Header\")\n+ var\n+ FeatureTelemetry: Codeunit \"Feature Telemetry\";\n+ CustomDimensions: Dictionary of [Text, Text];\n+ begin\n+ if not ExpenseReportHeader.\"Merchant Approved\" then\n+ Error(MerchantInvalidErr, ExpenseReportHeader.\"Merchant Name\", ExpenseReportHeader.\"No.\");\n+\n+ CustomDimensions.Add('EmployeeNo', ExpenseReportHeader.\"Employee No.\");\n+ FeatureTelemetry.LogUsage('0000EA2', FeatureNameTxt, ReportReleasedTxt, CustomDimensions);\n+ end;\n+}\n", "expected_comments": [{"file": "src/ExpenseUser.Table.al", "line_start": 41, "line_end": 41, "domain": "privacy", "body": "StrSubstNo pre-builds error message with PII (Full Name and E-Mail) and passes it to Error() — PII leaks to telemetry — Use direct Error substitution or omit PII", "severity": "high"}, {"file": "src/JobQueueErrorHandler.Codeunit.al", "line_start": 8, "line_end": 8, "domain": "privacy", "body": "GetLastErrorText passed through StrSubstNo into Session.LogMessage telemetry — may contain customer content — Log a generic message and keep dynamic error detail out of telemetry", "severity": "medium"}, {"file": "src/ReleaseExpenseDocument.Codeunit.al", "line_start": 14, "line_end": 14, "domain": "privacy", "body": "StrSubstNo pre-builds error message containing Merchant Name (CustomerContent) and passes to Error() — leaks to telemetry — Use direct substitution in Error()", "severity": "medium"}, {"file": "src/ReleaseExpenseDocument.Codeunit.al", "line_start": 16, "line_end": 16, "domain": "privacy", "body": "Employee No. included as a telemetry custom dimension — can identify individuals — Remove EmployeeNo from telemetry dimensions or use a hash", "severity": "medium"}, {"file": "src/ReleaseExpReportDocument.Codeunit.al", "line_start": 14, "line_end": 14, "domain": "privacy", "body": "StrSubstNo pre-builds error message containing Merchant Name (CustomerContent) and passes to Error() — leaks to telemetry — Use direct substitution in Error()", "severity": "medium"}, {"file": "src/ReleaseExpReportDocument.Codeunit.al", "line_start": 16, "line_end": 16, "domain": "privacy", "body": "Employee No. included as a telemetry custom dimension — can identify individuals — Remove EmployeeNo from telemetry dimensions or use a hash", "severity": "medium"}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "True positive privacy findings: error_message_pii (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-013", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/PRReviewManager.Codeunit.al b/src/PRReviewManager.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PRReviewManager.Codeunit.al\n@@ -0,0 +1,34 @@\n+codeunit 57400 \"PR Review Manager\"\n+{\n+ Access = Internal;\n+\n+ var\n+ ReviewSubjectTxt: Label 'PR #%1 needs your review on %2', Comment = '%1 = PR number, %2 = branch name';\n+\n+ procedure GetDefaultReviewers(TargetBranch: Text): List of [Text]\n+ var\n+ Reviewers: List of [Text];\n+ begin\n+ Reviewers.Add('john.doe@contoso.com');\n+ Reviewers.Add('jane.smith@contoso.com');\n+ Reviewers.Add('mike.wilson@contoso.com');\n+ if TargetBranch = 'release' then\n+ Reviewers.Add('sarah.connor@contoso.com');\n+ exit(Reviewers);\n+ end;\n+\n+ procedure NotifyReviewers(PRNumber: Integer; TargetBranch: Text)\n+ var\n+ Reviewers: List of [Text];\n+ Reviewer: Text;\n+ begin\n+ Reviewers := GetDefaultReviewers(TargetBranch);\n+ foreach Reviewer in Reviewers do\n+ SendReviewNotification(Reviewer, StrSubstNo(ReviewSubjectTxt, PRNumber, TargetBranch));\n+ end;\n+\n+ local procedure SendReviewNotification(EmailAddress: Text; Subject: Text)\n+ begin\n+ // Sends the review notification email to the reviewer.\n+ end;\n+}\ndiff --git a/src/DeploymentConfig.Codeunit.al b/src/DeploymentConfig.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/DeploymentConfig.Codeunit.al\n@@ -0,0 +1,9 @@\n+codeunit 57401 \"Deployment Config\"\n+{\n+ Access = Internal;\n+\n+ procedure GetDeploymentNotificationRecipients(): Text\n+ begin\n+ exit('john.doe@contoso.com;jane.smith@contoso.com;mike.wilson@contoso.com');\n+ end;\n+}\n", "expected_comments": [{"file": "src/PRReviewManager.Codeunit.al", "line_start": 12, "line_end": 12, "domain": "privacy", "severity": "medium", "body": "Hardcoded personal email addresses embedded in the reviewer list. Personal data must not be hardcoded in source — move to a configuration table or setup record."}, {"file": "src/DeploymentConfig.Codeunit.al", "line_start": 7, "line_end": 7, "domain": "privacy", "severity": "medium", "body": "Hardcoded personal email addresses in source code for deployment notifications. Move recipient addresses to a configuration/setup table."}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "True positive privacy findings: hardcoded personal email addresses embedded directly in source code", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-014", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/ContactSyncProcessor.Codeunit.al b/src/ContactSyncProcessor.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ContactSyncProcessor.Codeunit.al\n@@ -0,0 +1,20 @@\n+codeunit 50160 \"Contact Sync Processor\"\n+{\n+ Access = Internal;\n+\n+ var\n+ SyncStartedTxt: Label 'Contact sync started for user %1.', Comment = '%1 = User Security ID';\n+ SyncDoneTxt: Label 'Contact sync completed for %1 (%2).', Comment = '%1 = Contact Name, %2 = Email';\n+\n+ procedure ProcessContactSync(ContactSyncUser: Record \"Contact Sync User\")\n+ var\n+ Telemetry: Codeunit Telemetry;\n+ CustomDimensions: Dictionary of [Text, Text];\n+ begin\n+ Telemetry.LogMessage('0000CS01', StrSubstNo(SyncStartedTxt, ContactSyncUser.\"User Security ID\"),\n+ Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CustomDimensions);\n+\n+ Telemetry.LogMessage('0000CS02', StrSubstNo(SyncDoneTxt, ContactSyncUser.\"Contact Name\", ContactSyncUser.\"Email Address\"),\n+ Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CustomDimensions);\n+ end;\n+}\ndiff --git a/src/ExpenseNotificationDispatcher.Codeunit.al b/src/ExpenseNotificationDispatcher.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExpenseNotificationDispatcher.Codeunit.al\n@@ -0,0 +1,16 @@\n+codeunit 50161 \"Expense Notification Dispatcher\"\n+{\n+ Access = Internal;\n+\n+ var\n+ NotifSentTxt: Label 'Notification sent for employee %1.', Comment = '%1 = Employee No.';\n+\n+ procedure DispatchNotification(ExpenseHeader: Record \"Expense Report Header\")\n+ var\n+ Telemetry: Codeunit Telemetry;\n+ CustomDimensions: Dictionary of [Text, Text];\n+ begin\n+ Telemetry.LogMessage('0000EA10', StrSubstNo(NotifSentTxt, ExpenseHeader.\"Employee No.\"),\n+ Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CustomDimensions);\n+ end;\n+}\ndiff --git a/src/InstallExpenseAgentSetup.Codeunit.al b/src/InstallExpenseAgentSetup.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/InstallExpenseAgentSetup.Codeunit.al\n@@ -0,0 +1,25 @@\n+codeunit 50162 \"Install Expense Agent Setup\"\n+{\n+ Subtype = Install;\n+\n+ var\n+ JitProvisionTxt: Label 'Expense agent provisioned for external user %1.', Comment = '%1 = User name';\n+\n+ trigger OnInstallAppPerCompany()\n+ var\n+ AgentSetup: Record \"Expense Agent Setup\";\n+ begin\n+ if not AgentSetup.Get() then\n+ exit;\n+ LogProvisioning(AgentSetup.\"External User Name\");\n+ end;\n+\n+ local procedure LogProvisioning(ExternalUserName: Text[100])\n+ var\n+ Telemetry: Codeunit Telemetry;\n+ CustomDimensions: Dictionary of [Text, Text];\n+ begin\n+ Telemetry.LogMessage('0000IN01', StrSubstNo(JitProvisionTxt, ExternalUserName),\n+ Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CustomDimensions);\n+ end;\n+}\n", "expected_comments": [{"file": "src/ContactSyncProcessor.Codeunit.al", "line_start": 14, "line_end": 14, "domain": "privacy", "body": "Telemetry message embeds the User Security ID (EndUserPseudonymousIdentifiers) but the LogMessage DataClassification is SystemMetadata. — Remove the User Security ID from the telemetry message or classify the entry as EndUserPseudonymousIdentifiers.", "severity": "high"}, {"file": "src/ContactSyncProcessor.Codeunit.al", "line_start": 17, "line_end": 17, "domain": "privacy", "body": "Telemetry message embeds the contact name and email address (EndUserIdentifiableInformation) under SystemMetadata classification. — Do not put contact name or email in telemetry messages; log a non-identifying key instead.", "severity": "high"}, {"file": "src/ExpenseNotificationDispatcher.Codeunit.al", "line_start": 13, "line_end": 13, "domain": "privacy", "body": "Telemetry message embeds the Employee No. (EndUserIdentifiableInformation) under SystemMetadata classification. — Remove the Employee No. from the telemetry message.", "severity": "high"}, {"file": "src/InstallExpenseAgentSetup.Codeunit.al", "line_start": 22, "line_end": 22, "domain": "privacy", "body": "Telemetry message embeds an external user name (EndUserIdentifiableInformation) under SystemMetadata classification. — Remove the user name from the telemetry message.", "severity": "high"}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "True positive privacy findings: logging_pii (trimmed to 3 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__privacy-015", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "privacy"}, "patch": "diff --git a/src/AIContextBuilder.Codeunit.al b/src/AIContextBuilder.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/AIContextBuilder.Codeunit.al\n@@ -0,0 +1,34 @@\n+codeunit 57500 \"AI Context Builder\"\n+{\n+ Access = Public;\n+\n+ procedure BuildTaskExecutionContext(AgentTaskId: Guid): Text\n+ var\n+ AgentTask: Record \"Agent Task\";\n+ User: Record User;\n+ ContextBuilder: TextBuilder;\n+ begin\n+ AgentTask.GetBySystemId(AgentTaskId);\n+ ContextBuilder.AppendLine('Task: ' + AgentTask.Description);\n+ ContextBuilder.AppendLine('Status: ' + Format(AgentTask.Status));\n+ if User.Get(AgentTask.\"Agent User Security ID\") then\n+ ContextBuilder.AppendLine('Created By: ' + User.\"Full Name\");\n+ exit(ContextBuilder.ToText());\n+ end;\n+\n+ procedure SendContextToAIService(Context: Text)\n+ var\n+ HttpClient: HttpClient;\n+ Content: HttpContent;\n+ Response: HttpResponseMessage;\n+ begin\n+ Content.WriteFrom(Context);\n+ HttpClient.Post(AiServiceUrlTok, Content, Response);\n+ if not Response.IsSuccessStatusCode() then\n+ Error(AiServiceErr);\n+ end;\n+\n+ var\n+ AiServiceErr: Label 'The AI evaluation service could not be reached.';\n+ AiServiceUrlTok: Label 'https://ai-evaluation.internal.example.com/evaluate', Locked = true;\n+}\ndiff --git a/src/CustomerDataExporter.Codeunit.al b/src/CustomerDataExporter.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerDataExporter.Codeunit.al\n@@ -0,0 +1,29 @@\n+codeunit 57501 \"Customer Data Exporter\"\n+{\n+ Access = Public;\n+\n+ procedure ExportCustomerDataToPartner(CustomerNo: Code[20])\n+ var\n+ Customer: Record Customer;\n+ HttpClient: HttpClient;\n+ Content: HttpContent;\n+ ContentHeaders: HttpHeaders;\n+ Response: HttpResponseMessage;\n+ Payload: JsonObject;\n+ PayloadText: Text;\n+ begin\n+ Customer.Get(CustomerNo);\n+ Payload.Add('customerName', Customer.Name);\n+ Payload.Add('email', Customer.\"E-Mail\");\n+ Payload.Add('phone', Customer.\"Phone No.\");\n+ Payload.Add('address', Customer.Address);\n+ Payload.WriteTo(PayloadText);\n+ Content.WriteFrom(PayloadText);\n+ Content.GetHeaders(ContentHeaders);\n+ ContentHeaders.Add('Content-Type', 'application/json');\n+ HttpClient.Post(PartnerApiUrlTok, Content, Response);\n+ end;\n+\n+ var\n+ PartnerApiUrlTok: Label 'https://partner-api.contoso.com/customers/sync', Locked = true;\n+}\ndiff --git a/src/ExternalCRMSync.Codeunit.al b/src/ExternalCRMSync.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExternalCRMSync.Codeunit.al\n@@ -0,0 +1,42 @@\n+codeunit 57300 \"External CRM Sync\"\n+{\n+ Access = Public;\n+\n+ procedure SyncCustomerToExternalCRM(Customer: Record Customer)\n+ var\n+ HttpClient: HttpClient;\n+ Content: HttpContent;\n+ ContentHeaders: HttpHeaders;\n+ Response: HttpResponseMessage;\n+ Payload: JsonObject;\n+ PayloadText: Text;\n+ begin\n+ if Customer.\"E-Mail\" = '' then\n+ exit;\n+ Payload.Add('email', Customer.\"E-Mail\");\n+ Payload.Add('name', Customer.Name);\n+ Payload.Add('phone', Customer.\"Phone No.\");\n+ Payload.Add('address', Customer.Address);\n+ Payload.WriteTo(PayloadText);\n+ Content.WriteFrom(PayloadText);\n+ Content.GetHeaders(ContentHeaders);\n+ ContentHeaders.Add('Content-Type', 'application/json');\n+ HttpClient.Post(CrmSyncUrlTok, Content, Response);\n+ end;\n+\n+ procedure SyncAllPendingCustomers()\n+ var\n+ Customer: Record Customer;\n+ begin\n+ Customer.SetRange(\"CRM Sync Required\", true);\n+ if Customer.FindSet(true) then\n+ repeat\n+ SyncCustomerToExternalCRM(Customer);\n+ Customer.\"CRM Sync Required\" := false;\n+ Customer.Modify(true);\n+ until Customer.Next() = 0;\n+ end;\n+\n+ var\n+ CrmSyncUrlTok: Label 'https://api.example.com/contacts/sync', Locked = true;\n+}\ndiff --git a/src/OutboxEmailDispatcher.Codeunit.al b/src/OutboxEmailDispatcher.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/OutboxEmailDispatcher.Codeunit.al\n@@ -0,0 +1,57 @@\n+codeunit 57301 \"Outbox Email Dispatcher\"\n+{\n+ Access = Public;\n+\n+ procedure SendPendingEmails()\n+ var\n+ OutboxEmail: Record \"EA Outbox Email\";\n+ HttpClient: HttpClient;\n+ Content: HttpContent;\n+ ContentHeaders: HttpHeaders;\n+ Response: HttpResponseMessage;\n+ PayloadText: Text;\n+ begin\n+ OutboxEmail.SetRange(\"Send Status\", OutboxEmail.\"Send Status\"::Pending);\n+ if OutboxEmail.FindSet(true) then\n+ repeat\n+ PayloadText := BuildMailPayload(OutboxEmail);\n+ Content.WriteFrom(PayloadText);\n+ Content.GetHeaders(ContentHeaders);\n+ ContentHeaders.Add('Content-Type', 'application/json');\n+ if HttpClient.Post(GraphSendMailUrlTok, Content, Response) then begin\n+ if Response.IsSuccessStatusCode() then\n+ OutboxEmail.\"Send Status\" := OutboxEmail.\"Send Status\"::Sent\n+ else\n+ OutboxEmail.\"Send Status\" := OutboxEmail.\"Send Status\"::Failed;\n+ OutboxEmail.Modify(true);\n+ end;\n+ until OutboxEmail.Next() = 0;\n+ end;\n+\n+ local procedure BuildMailPayload(OutboxEmail: Record \"EA Outbox Email\"): Text\n+ var\n+ Message: JsonObject;\n+ Body: JsonObject;\n+ Recipient: JsonObject;\n+ EmailAddress: JsonObject;\n+ ToRecipients: JsonArray;\n+ Root: JsonObject;\n+ PayloadText: Text;\n+ begin\n+ Body.Add('contentType', 'HTML');\n+ Body.Add('content', OutboxEmail.GetBodyText());\n+ EmailAddress.Add('address', OutboxEmail.\"To Line\");\n+ Recipient.Add('emailAddress', EmailAddress);\n+ ToRecipients.Add(Recipient);\n+ Message.Add('subject', OutboxEmail.Subject);\n+ Message.Add('toRecipients', ToRecipients);\n+ Message.Add('body', Body);\n+ Root.Add('message', Message);\n+ Root.Add('saveToSentItems', true);\n+ Root.WriteTo(PayloadText);\n+ exit(PayloadText);\n+ end;\n+\n+ var\n+ GraphSendMailUrlTok: Label 'https://graph.microsoft.com/v1.0/me/sendMail', Locked = true;\n+}\n", "expected_comments": [{"file": "src/AIContextBuilder.Codeunit.al", "line_start": 26, "line_end": 26, "domain": "privacy", "severity": "high", "body": "Outgoing HTTP request to external AI service sends context containing user data with no Privacy Notice consent check in the code path — verify consent before transmitting."}, {"file": "src/CustomerDataExporter.Codeunit.al", "line_start": 24, "line_end": 24, "domain": "privacy", "severity": "critical", "body": "Customer PII (name, email, phone, address) sent to external partner API with no Privacy Notice consent check in the code path — verify consent before transmitting."}, {"file": "src/ExternalCRMSync.Codeunit.al", "line_start": 24, "line_end": 24, "domain": "privacy", "severity": "critical", "body": "Customer PII (email, name, phone, address) sent to external CRM service with no Privacy Notice consent check in the code path — verify consent before transmitting."}, {"file": "src/OutboxEmailDispatcher.Codeunit.al", "line_start": 21, "line_end": 21, "domain": "privacy", "severity": "high", "body": "Outgoing HTTP request to Microsoft Graph API sends email content with no Privacy Notice consent check in the code path — verify consent before transmitting."}], "match_line_tolerance": 2, "domain": "privacy", "category": "code-review", "description": "True positive privacy findings: PII sent to external services without privacy consent checks or data minimization (4 findings across 4 files)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-001", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/CleanCaptionSetup.Page.al b/src/CleanCaptionSetup.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CleanCaptionSetup.Page.al\n@@ -0,0 +1,37 @@\n+page 50001 \"Clean Caption Setup\"\n+{\n+ Caption = 'Clean Caption Setup';\n+ PageType = Card;\n+ ApplicationArea = All;\n+ UsageCategory = Administration;\n+ SourceTable = Vendor;\n+\n+ layout\n+ {\n+ area(content)\n+ {\n+ group(General)\n+ {\n+ Caption = 'General';\n+ field(VendorNo; Rec.\"No.\")\n+ {\n+ ApplicationArea = All;\n+ Caption = 'No.';\n+ ToolTip = 'Specifies the number of the vendor.';\n+ }\n+ field(VendorName; Rec.Name)\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Name';\n+ ToolTip = 'Specifies the name of the vendor.';\n+ }\n+ field(VendorBalance; Rec.\"Balance (LCY)\")\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Balance (LCY)';\n+ ToolTip = 'Specifies the balance in local currency.';\n+ }\n+ }\n+ }\n+ }\n+}\ndiff --git a/src/IndentationViolation.Codeunit.al b/src/IndentationViolation.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/IndentationViolation.Codeunit.al\n@@ -0,0 +1,17 @@\n+codeunit 50341 \"Indentation Violation\"\n+{\n+ local procedure AdjustCount(StartCount: Integer; Increment: Integer): Integer\n+ var\n+ Counter: Integer;\n+ begin\n+ Counter := StartCount;\n+ if Increment > 0 then\n+ Counter := Counter + Increment;\n+ exit(Counter);\n+ end;\n+\n+ local procedure IsEnabled(Enabled: Boolean): Boolean\n+ begin\n+ exit(Enabled);\n+ end;\n+}\n", "expected_comments": [{"file": "src/IndentationViolation.Codeunit.al", "line_start": 3, "line_end": 16, "severity": "low", "domain": "style", "body": "File uses 4-space indentation for nested AL blocks. Project style requires 2-space indentation consistently throughout."}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "False positive style findings: caption_false_positive (790 false positives). Agent flagged these but reviewers rejected them.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-002", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/PostingHelper.Codeunit.al b/src/PostingHelper.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PostingHelper.Codeunit.al\n@@ -0,0 +1,33 @@\n+codeunit 50200 \"Posting Helper\"\n+{\n+ var\n+ PostedMsg: Label 'Document %1 posted successfully.', Comment = '%1 = Document No.';\n+\n+ /// \n+ /// Validates that the sales header is ready for posting.\n+ /// \n+ /// The sales header record to validate.\n+ /// True if the header is released and has lines; otherwise, false.\n+ procedure ValidateSalesHeader(SalesHeader: Record \"Sales Header\"): Boolean\n+ var\n+ SalesLine: Record \"Sales Line\";\n+ begin\n+ if SalesHeader.Status <> SalesHeader.Status::Released then\n+ exit(false);\n+\n+ SalesLine.SetRange(\"Document Type\", SalesHeader.\"Document Type\");\n+ SalesLine.SetRange(\"Document No.\", SalesHeader.\"No.\");\n+ exit(not SalesLine.IsEmpty());\n+ end;\n+\n+ /// \n+ /// Gets the posting confirmation message.\n+ /// \n+ /// The document number to include in the message.\n+ /// The formatted posting confirmation message.\n+ procedure GetPostingMessage(DocumentNo: Code[20]): Text\n+ begin\n+ exit(StrSubstNo(PostedMsg, DocumentNo));\n+ end;\n+}\n+\ndiff --git a/src/SelfReferenceStyle.Codeunit.al b/src/SelfReferenceStyle.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SelfReferenceStyle.Codeunit.al\n@@ -0,0 +1,17 @@\n+codeunit 50342 \"Self Reference Style\"\n+{\n+ local procedure RunCheck(CustomerNo: Code[20]): Boolean\n+ begin\n+ exit(IsCustomerNoFilled(CustomerNo));\n+ end;\n+\n+ local procedure IsCustomerNoFilled(CustomerNo: Code[20]): Boolean\n+ begin\n+ exit(CustomerNo <> '');\n+ end;\n+\n+ local procedure EchoCustomerNo(CustomerNo: Code[20]): Code[20]\n+ begin\n+ exit(CustomerNo);\n+ end;\n+}\n", "expected_comments": [{"file": "src/PostingHelper.Codeunit.al", "line_start": 3, "line_end": 33, "severity": "low", "domain": "style", "body": "The codeunit body uses 4-space indentation for nested AL blocks. Project style requires 2-space indentation consistently throughout."}, {"file": "src/SelfReferenceStyle.Codeunit.al", "line_start": 5, "line_end": 5, "severity": "low", "domain": "style", "body": "Self-references inside codeunits should be qualified with this. Use this.IsCustomerNoFilled(CustomerNo) for clarity."}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "Well-structured codeunit with PascalCase naming, Label for messages, and clean formatting", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-003", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/Page50200.ItemList.al b/src/Page50200.ItemList.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/Page50200.ItemList.al\n@@ -0,0 +1,49 @@\n+page 50200 \"Item List Extension\"\n+{\n+ PageType = List;\n+ SourceTable = Item;\n+ ApplicationArea = All;\n+ Caption = 'Item List Extension';\n+\n+ layout\n+ {\n+ area(Content)\n+ {\n+ repeater(General)\n+ {\n+ field(ItemNo; Rec.\"No.\")\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Item No.';\n+ ToolTip = 'Specifies the item number.';\n+ }\n+ field(Description; Rec.Description)\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Description';\n+ ToolTip = 'Specifies the item description.';\n+ }\n+ }\n+ }\n+ }\n+\n+ actions\n+ {\n+ area(Processing)\n+ {\n+ action(RefreshData)\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Refresh';\n+ ToolTip = 'Refreshes the item list.';\n+ Image = Refresh;\n+\n+ trigger OnAction()\n+ begin\n+ CurrPage.Update(false);\n+ end;\n+ }\n+ }\n+ }\n+}\n+\ndiff --git a/src/HungarianVariableStyle.Codeunit.al b/src/HungarianVariableStyle.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/HungarianVariableStyle.Codeunit.al\n@@ -0,0 +1,15 @@\n+codeunit 50343 \"Hungarian Variable Style\"\n+{\n+ local procedure CopyCount(StartCount: Integer): Integer\n+ var\n+ iCount: Integer;\n+ begin\n+ iCount := StartCount;\n+ exit(iCount);\n+ end;\n+\n+ local procedure ReturnEnabled(Enabled: Boolean): Boolean\n+ begin\n+ exit(Enabled);\n+ end;\n+}\n", "expected_comments": [{"file": "src/HungarianVariableStyle.Codeunit.al", "line_start": 5, "line_end": 5, "severity": "low", "domain": "style", "body": "Variable name iCount uses a Hungarian-style prefix. AL variables should use descriptive PascalCase names without type prefixes."}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "Well-structured page with proper captions, tooltips, naming conventions, and ApplicationArea", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-004", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/CleanDocumentation.Codeunit.al b/src/CleanDocumentation.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CleanDocumentation.Codeunit.al\n@@ -0,0 +1,35 @@\n+codeunit 50002 \"Clean Documentation\"\n+{\n+ Permissions = tabledata Customer = R;\n+\n+ /// \n+ /// Validates a customer record for completeness.\n+ /// \n+ /// The customer number to validate.\n+ /// True if the customer is valid.\n+ procedure ValidateCustomer(CustomerNo: Code[20]): Boolean\n+ var\n+ Customer: Record Customer;\n+ begin\n+ if not Customer.Get(CustomerNo) then\n+ exit(false);\n+\n+ exit(Customer.Name <> '');\n+ end;\n+\n+ /// \n+ /// Calculates the total balance for a customer.\n+ /// \n+ /// The customer number.\n+ /// The total balance amount.\n+ procedure GetCustomerBalance(CustomerNo: Code[20]): Decimal\n+ var\n+ Customer: Record Customer;\n+ begin\n+ if not Customer.Get(CustomerNo) then\n+ exit(0);\n+\n+ Customer.CalcFields(\"Balance (LCY)\");\n+ exit(Customer.\"Balance (LCY)\");\n+ end;\n+}\ndiff --git a/src/Page50344.FileNameStyle.al b/src/Page50344.FileNameStyle.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/Page50344.FileNameStyle.al\n@@ -0,0 +1,17 @@\n+codeunit 50344 \"File Name Style\"\n+{\n+ local procedure EchoText(InputText: Text): Text\n+ begin\n+ exit(InputText);\n+ end;\n+\n+ local procedure EchoCode(InputCode: Code[20]): Code[20]\n+ begin\n+ exit(InputCode);\n+ end;\n+\n+ local procedure IsReady(Ready: Boolean): Boolean\n+ begin\n+ exit(Ready);\n+ end;\n+}\n", "expected_comments": [{"file": "src/Page50344.FileNameStyle.al", "line_start": 1, "line_end": 1, "severity": "low", "domain": "style", "body": "File name does not follow the ..al pattern. This codeunit should be in FileNameStyle.Codeunit.al."}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "False positive style findings: documentation_fp (192 false positives). Agent flagged these but reviewers rejected them.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-005", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/CleanErrorHandling.Codeunit.al b/src/CleanErrorHandling.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CleanErrorHandling.Codeunit.al\n@@ -0,0 +1,32 @@\n+codeunit 50003 \"Clean Error Handling\"\n+{\n+ var\n+ CustomerNotFoundErr: Label 'Customer %1 was not found.', Comment = '%1 = Customer No.';\n+\n+ /// \n+ /// Processes a customer record with proper error handling.\n+ /// \n+ /// The customer number to process.\n+ procedure ProcessCustomer(CustomerNo: Code[20])\n+ var\n+ Customer: Record Customer;\n+ begin\n+ if not Customer.Get(CustomerNo) then\n+ Error(CustomerNotFoundErr, CustomerNo);\n+\n+ Customer.TestField(Name);\n+ end;\n+\n+ /// \n+ /// Attempts to validate a single customer record.\n+ /// \n+ /// The customer number to validate.\n+ [TryFunction]\n+ procedure TryValidateCustomer(CustomerNo: Code[20])\n+ var\n+ Customer: Record Customer;\n+ begin\n+ Customer.Get(CustomerNo);\n+ Customer.TestField(Name);\n+ end;\n+}\ndiff --git a/src/LineStartStyle.Codeunit.al b/src/LineStartStyle.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/LineStartStyle.Codeunit.al\n@@ -0,0 +1,17 @@\n+codeunit 50345 \"Line Start Style\"\n+{\n+ local procedure SumRange(StartIndex: Integer; LimitCount: Integer): Integer\n+ var\n+ Index: Integer;\n+ Counter: Integer;\n+ begin\n+ for Index := StartIndex to LimitCount do begin\n+ Counter := Counter + Index; end;\n+ exit(Counter);\n+ end;\n+\n+ local procedure EchoCount(CountValue: Integer): Integer\n+ begin\n+ exit(CountValue);\n+ end;\n+}\n", "expected_comments": [{"file": "src/LineStartStyle.Codeunit.al", "line_start": 9, "line_end": 9, "severity": "low", "domain": "style", "body": "The end keyword appears after another statement on the same line. END, IF, REPEAT, UNTIL, FOR, WHILE, and CASE statements should start a line."}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "False positive style findings: error_handling_fp (2 false positives). Agent flagged these but reviewers rejected them.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-006", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/CleanFormatting.Codeunit.al b/src/CleanFormatting.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CleanFormatting.Codeunit.al\n@@ -0,0 +1,35 @@\n+codeunit 50004 \"Clean Formatting\"\n+{\n+ /// \n+ /// Applies a discount percentage to a sales line.\n+ /// \n+ /// The sales line to update.\n+ /// The discount percentage to apply.\n+ procedure ApplyDiscount(var SalesLine: Record \"Sales Line\"; DiscountPct: Decimal)\n+ begin\n+ if DiscountPct <= 0 then\n+ exit;\n+\n+ if DiscountPct > 100 then\n+ DiscountPct := 100;\n+\n+ SalesLine.\"Line Discount %\" := DiscountPct;\n+ SalesLine.Modify(true);\n+ end;\n+\n+ /// \n+ /// Finds the unit price for an item.\n+ /// \n+ /// The item number to look up.\n+ /// The unit price of the item.\n+ procedure FindUnitPrice(ItemNo: Code[20]): Decimal\n+ var\n+ Item: Record Item;\n+ begin\n+ Item.SetLoadFields(\"Unit Price\");\n+ if Item.Get(ItemNo) then\n+ exit(Item.\"Unit Price\");\n+\n+ exit(0);\n+ end;\n+}\ndiff --git a/src/BeginPlacementStyle.Codeunit.al b/src/BeginPlacementStyle.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/BeginPlacementStyle.Codeunit.al\n@@ -0,0 +1,17 @@\n+codeunit 50346 \"Begin Placement Style\"\n+{\n+ local procedure ClampAmount(Amount: Decimal; MaximumAmount: Decimal): Decimal\n+ begin\n+ if Amount > MaximumAmount then\n+ begin\n+ Amount := MaximumAmount;\n+ MaximumAmount := Amount;\n+ end;\n+ exit(Amount);\n+ end;\n+\n+ local procedure EchoAmount(Amount: Decimal): Decimal\n+ begin\n+ exit(Amount);\n+ end;\n+}\n", "expected_comments": [{"file": "src/BeginPlacementStyle.Codeunit.al", "line_start": 6, "line_end": 6, "severity": "low", "domain": "style", "body": "BEGIN follows THEN on a separate line. Place begin on the same line as then: if Amount > MaximumAmount then begin."}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "False positive style findings: formatting_fp (30 false positives). Agent flagged these but reviewers rejected them.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-007", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/CleanNaming.Codeunit.al b/src/CleanNaming.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CleanNaming.Codeunit.al\n@@ -0,0 +1,21 @@\n+codeunit 50005 \"Clean Naming\"\n+{\n+ /// \n+ /// Builds a display label for a customer.\n+ /// \n+ /// The customer number.\n+ /// The customer name.\n+ /// The formatted display label.\n+ procedure FormatCustomerLabel(CustomerNo: Code[20]; CustomerName: Text): Text\n+ begin\n+ exit(this.BuildLabel(CustomerNo, CustomerName));\n+ end;\n+\n+ local procedure BuildLabel(CustomerNo: Code[20]; CustomerName: Text): Text\n+ begin\n+ if CustomerName = '' then\n+ exit(CustomerNo);\n+\n+ exit(CustomerNo + ' - ' + CustomerName);\n+ end;\n+}\ndiff --git a/src/CaseFormattingStyle.Codeunit.al b/src/CaseFormattingStyle.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CaseFormattingStyle.Codeunit.al\n@@ -0,0 +1,17 @@\n+codeunit 50347 \"Case Formatting Style\"\n+{\n+ local procedure GetStatusName(StatusCode: Code[1]): Text\n+ begin\n+ case StatusCode of\n+ 'A': exit('Active');\n+ 'B': exit('Blocked');\n+ else\n+ exit('Unknown');\n+ end;\n+ end;\n+\n+ local procedure EchoStatus(StatusName: Text): Text\n+ begin\n+ exit(StatusName);\n+ end;\n+}\n", "expected_comments": [{"file": "src/CaseFormattingStyle.Codeunit.al", "line_start": 6, "line_end": 7, "severity": "low", "domain": "style", "body": "CASE branch actions should start on the line after the possibility. Move the exit statements under the 'A' and 'B' labels."}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "False positive style findings: naming_false_positive (57 false positives). Agent flagged these but reviewers rejected them.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-008", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/CustomerIntegrationSetup.table.al b/src/CustomerIntegrationSetup.table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerIntegrationSetup.table.al\n@@ -0,0 +1,40 @@\n+table 50003 \"Customer Integration Setup\"\n+{\n+ Caption = 'Customer Integration Setup';\n+\n+ fields\n+ {\n+ field(1; \"Primary Key\"; Code[10])\n+ {\n+ Caption = 'Primary Key';\n+ DataClassification = SystemMetadata;\n+ ToolTip = 'Specifies the primary key of the setup record.';\n+ }\n+ field(2; \"API Endpoint\"; Text[250])\n+ {\n+ Caption = 'API Endpoint';\n+ DataClassification = CustomerContent;\n+ ToolTip = 'Specifies the API integration endpoint.';\n+ }\n+ field(3; \"Connection Name\"; Text[100])\n+ {\n+ Caption = 'Connection Name';\n+ DataClassification = CustomerContent;\n+ ToolTip = 'Specifies the display name of the integration connection.';\n+ }\n+ field(4; \"Enable Integration\"; Boolean)\n+ {\n+ Caption = 'Enable Integration';\n+ DataClassification = CustomerContent;\n+ ToolTip = 'Specifies whether the integration is enabled.';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Primary Key\")\n+ {\n+ Clustered = true;\n+ }\n+ }\n+}\ndiff --git a/src/PascalCaseStyle.Codeunit.al b/src/PascalCaseStyle.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PascalCaseStyle.Codeunit.al\n@@ -0,0 +1,17 @@\n+codeunit 50348 \"Pascal Case Style\"\n+{\n+ local procedure calculateTotal(Amount: Decimal; TaxAmount: Decimal): Decimal\n+ begin\n+ exit(Amount + TaxAmount);\n+ end;\n+\n+ local procedure EchoAmount(Amount: Decimal): Decimal\n+ begin\n+ exit(Amount);\n+ end;\n+\n+ local procedure IsGreaterThan(Amount: Decimal; MinimumAmount: Decimal): Boolean\n+ begin\n+ exit(Amount > MinimumAmount);\n+ end;\n+}\n", "expected_comments": [{"file": "src/PascalCaseStyle.Codeunit.al", "line_start": 3, "line_end": 3, "severity": "low", "domain": "style", "body": "Procedure name calculateTotal is not PascalCase. AL function names should use PascalCase, such as CalculateTotal."}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "Clean obsolete patterns with correct ObsoleteState, ObsoleteReason, ObsoleteTag, and Obsolete attribute usage", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-009", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/CleanOtherStyle.Page.al b/src/CleanOtherStyle.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CleanOtherStyle.Page.al\n@@ -0,0 +1,59 @@\n+page 50006 \"Customer Detail Card\"\n+{\n+ Caption = 'Customer Detail Card';\n+ PageType = Card;\n+ ApplicationArea = All;\n+ UsageCategory = Administration;\n+ SourceTable = Customer;\n+ AboutTitle = 'About customer detail cards';\n+ AboutText = 'Use this page to view and manage customer information.';\n+\n+ layout\n+ {\n+ area(content)\n+ {\n+ group(General)\n+ {\n+ Caption = 'General';\n+ field(CustomerNo; Rec.\"No.\")\n+ {\n+ ApplicationArea = All;\n+ Caption = 'No.';\n+ ToolTip = 'Specifies the customer number.';\n+ }\n+ field(CustomerName; Rec.Name)\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Name';\n+ ToolTip = 'Specifies the customer name.';\n+ }\n+ field(CustomerBalance; Rec.\"Balance (LCY)\")\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Balance (LCY)';\n+ ToolTip = 'Specifies the balance in local currency.';\n+ }\n+ }\n+ }\n+ }\n+\n+ actions\n+ {\n+ area(processing)\n+ {\n+ action(UpdateRecord)\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Update';\n+ ToolTip = 'Updates the customer record.';\n+ Image = Refresh;\n+\n+ trigger OnAction()\n+ begin\n+ CurrPage.Update(false);\n+ end;\n+ }\n+ }\n+ }\n+}\n+\ndiff --git a/src/OperatorSpacingStyle.Codeunit.al b/src/OperatorSpacingStyle.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/OperatorSpacingStyle.Codeunit.al\n@@ -0,0 +1,15 @@\n+codeunit 50349 \"Operator Spacing Style\"\n+{\n+ local procedure CalculateTotal(Amount: Decimal; Quantity: Decimal): Decimal\n+ var\n+ Total: Decimal;\n+ begin\n+ Total:=Amount*Quantity;\n+ exit(Total);\n+ end;\n+\n+ local procedure IsWithinLimit(Amount: Decimal; LimitAmount: Decimal): Boolean\n+ begin\n+ exit(Amount <= LimitAmount);\n+ end;\n+}\n", "expected_comments": [{"file": "src/OperatorSpacingStyle.Codeunit.al", "line_start": 7, "line_end": 7, "severity": "low", "domain": "style", "body": "Missing spaces around assignment and multiplication operators. AL style requires exactly one space on each side of binary operators."}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "False positive style findings: other_style (184 false positives). Agent flagged these but reviewers rejected them.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-010", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/ServiceMgtSetup.Page.al b/src/ServiceMgtSetup.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ServiceMgtSetup.Page.al\n@@ -0,0 +1,196 @@\n+page 50104 \"Service Mgt. Setup\"\n+{\n+ AccessByPermission = TableData \"Service Header\" = R;\n+ ApplicationArea = Service;\n+ Caption = 'Service Management Setup';\n+ DeleteAllowed = false;\n+ InsertAllowed = false;\n+ PageType = Card;\n+ SourceTable = \"Service Mgt. Setup\";\n+ UsageCategory = Administration;\n+\n+ layout\n+ {\n+ area(content)\n+ {\n+ group(General)\n+ {\n+ Caption = 'General';\n+ field(\"Service Order Nos.\"; Rec.\"Service Order Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to service orders.';\n+ }\n+ field(\"Service Quote Nos.\"; Rec.\"Service Quote Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to service quotes.';\n+ }\n+ field(\"Service Invoice Nos.\"; Rec.\"Service Invoice Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to service invoices.';\n+ }\n+ field(\"Service Credit Memo Nos.\"; Rec.\"Service Credit Memo Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to service credit memos.';\n+ }\n+ field(\"Posted Service Invoice Nos.\"; Rec.\"Posted Service Invoice Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to posted service invoices.';\n+ }\n+ field(\"Posted Serv. Credit Memo Nos.\"; Rec.\"Posted Serv. Credit Memo Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to posted service credit memos.';\n+ }\n+ field(\"Service Shipment Nos.\"; Rec.\"Service Shipment Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to service shipments.';\n+ }\n+ field(\"Loaner Nos.\"; Rec.\"Loaner Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to loaners.';\n+ }\n+ field(\"Service Item Nos.\"; Rec.\"Service Item Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to service items.';\n+ }\n+ field(\"Service Contract Nos.\"; Rec.\"Service Contract Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to service contracts.';\n+ }\n+ field(\"Contract Template Nos.\"; Rec.\"Contract Template Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to contract templates.';\n+ }\n+ field(\"Contract Invoice Nos.\"; Rec.\"Contract Invoice Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to contract invoices.';\n+ }\n+ field(\"Contract Credit Memo Nos.\"; Rec.\"Contract Credit Memo Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to contract credit memos.';\n+ }\n+ field(\"Troubleshooting Nos.\"; Rec.\"Troubleshooting Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series that will be used to assign numbers to troubleshooting guidelines.';\n+ }\n+ }\n+ group(Defaults)\n+ {\n+ Caption = 'Defaults';\n+ field(\"Default Response Time (Hours)\"; Rec.\"Default Response Time (Hours)\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the default response time in hours for new service orders.';\n+ }\n+ field(\"Service Order Type\"; Rec.\"Service Order Type\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the default service order type for new service orders.';\n+ }\n+ field(\"Default Warranty Duration\"; Rec.\"Default Warranty Duration\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the default warranty duration for service items.';\n+ }\n+ field(\"One Service Item Line/Order\"; Rec.\"One Service Item Line/Order\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies that service orders can contain only one service item line.';\n+ }\n+ field(\"Skip Manual Res. Alloc.\"; Rec.\"Skip Manual Res. Alloc.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies that the system should skip manual resource allocation when creating service orders.';\n+ }\n+ }\n+ group(Posting)\n+ {\n+ Caption = 'Posting';\n+ field(\"Enable Concurrent Posting\"; Rec.\"Ship-to Address\")\n+ {\n+ ApplicationArea = Service;\n+ Caption = 'Enable Concurrent Posting';\n+ ToolTip = 'Specifies whether concurrent posting is enabled.';\n+ }\n+ field(\"Posted Service Inv. Nos.\"; Rec.\"Posted Service Invoice Nos.\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies the code for the number series for posted service invoices.';\n+ Visible = false;\n+ }\n+ field(\"Logo Position on Documents\"; Rec.\"Logo Position on Documents\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies where the company logo appears on printed service documents.';\n+ }\n+ field(\"Fault Reason Code Mandatory\"; Rec.\"Fault Reason Code Mandatory\")\n+ {\n+ ApplicationArea = Service;\n+ ToolTip = 'Specifies that a fault reason code must be entered on service lines.';\n+ }\n+ }\n+ }\n+ }\n+\n+ actions\n+ {\n+ area(navigation)\n+ {\n+ action(\"Number Series\")\n+ {\n+ ApplicationArea = Service;\n+ Caption = 'Number Series';\n+ Image = NumberSetup;\n+ RunObject = Page \"No. Series\";\n+ ToolTip = 'Set up the number series from which a new number is automatically assigned to new cards and documents. You can set up a new number series or change existing number series.';\n+ }\n+ }\n+ area(processing)\n+ {\n+ action(\"Reset to Defaults\")\n+ {\n+ ApplicationArea = Service;\n+ Caption = 'Reset to Defaults';\n+ Image = Restore;\n+ ToolTip = 'Reset all settings to their default values.';\n+\n+ trigger OnAction()\n+ begin\n+ if Confirm('Are you sure you want to reset all settings to their default values?') then\n+ ResetToDefaults();\n+ end;\n+ }\n+ }\n+ }\n+\n+ trigger OnOpenPage()\n+ begin\n+ if not Rec.Get() then begin\n+ Rec.Init();\n+ Rec.Insert();\n+ end;\n+ end;\n+\n+ local procedure ResetToDefaults()\n+ begin\n+ Rec.\"Default Response Time (Hours)\" := 24;\n+ Rec.\"One Service Item Line/Order\" := true;\n+ Rec.\"Skip Manual Res. Alloc.\" := false;\n+ Rec.\"Fault Reason Code Mandatory\" := true;\n+ Rec.Modify(true);\n+ end;\n+}\n+\n", "expected_comments": [{"file": "src/ServiceMgtSetup.Page.al", "line_start": 122, "line_end": 122, "body": "Misleading field name: 'Enable Concurrent Posting' is bound to Rec.\"Ship-to Address\", which is a completely unrelated field. The Caption and field name suggest posting behavior but the source is an address field. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/ServiceMgtSetup.Page.al", "line_start": 172, "line_end": 172, "body": "Hardcoded text string in Confirm() call: 'Are you sure you want to reset all settings to their default values?' should use a Label variable with Qst suffix (CodeCop AA0217). — See agent comment for details.", "severity": "medium", "domain": "style"}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "True positive style findings: caption violations in service setup page", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-011", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/AgentTaskTemplate.Codeunit.al b/src/AgentTaskTemplate.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/AgentTaskTemplate.Codeunit.al\n@@ -0,0 +1,46 @@\n+codeunit 50105 \"Agent Task Template\"\n+{\n+ Access = Internal;\n+\n+ var\n+ TempBlob: Codeunit \"Temp Blob\";\n+ TemplateStream: InStream;\n+ TemplateOutStream: OutStream;\n+\n+ procedure ImportTemplate(FilePath: Text): Boolean\n+ var\n+ FileManagement: Codeunit \"File Management\";\n+ ImportFile: File;\n+ ImportInStream: InStream;\n+ TemplateRecord: Record \"Agent Template\";\n+ TemplateExists: Boolean;\n+ ValidationResult: Boolean;\n+ ProcessingError: Text;\n+ ImportSuccess: Boolean;\n+ DocumentType: Text;\n+ DocumentVersion: Text;\n+ DocumentContent: Text;\n+ ProcessingOptions: Record \"Template Processing Options\";\n+ FieldMapping: Record \"Field Mapping\";\n+ ValidationRules: Record \"Validation Rules\";\n+ TransformationRules: Record \"Transformation Rules\";\n+ OutputConfiguration: Record \"Output Configuration\";\n+ LoggingOptions: Record \"Logging Options\";\n+ SecuritySettings: Record \"Security Settings\";\n+ PerformanceSettings: Record \"Performance Settings\";\n+ ErrorHandlingSettings: Record \"Error Handling Settings\";\n+ NotificationSettings: Record \"Notification Settings\";\n+\n+ begin // begin keyword at line 78 - but more variables after\n+ // Initialize processing\n+ ImportSuccess := false;\n+ DocumentType := '';\n+\n+ if FilePath = '' then begin\n+ ProcessingError := 'File does not exist: ' + FilePath;\n+ exit(false);\n+ end;\n+\n+ exit(ImportSuccess);\n+ end;\n+}\ndiff --git a/src/NonDeductiblePurchPosting.Codeunit.al b/src/NonDeductiblePurchPosting.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/NonDeductiblePurchPosting.Codeunit.al\n@@ -0,0 +1,51 @@\n+codeunit 50108 \"Non-Deductible Purch. Posting\"\n+{\n+ Access = Internal;\n+\n+ var\n+ GeneralLedgerSetup: Record \"General Ledger Setup\";\n+ VATPostingSetup: Record \"VAT Posting Setup\";\n+ TempItemLedgerEntry: Record \"Item Ledger Entry\" temporary;\n+\n+ local procedure GetVATPostingSetup(PurchaseLine: Record \"Purchase Line\"): Boolean\n+ begin\n+ if VATPostingSetup.Get(PurchaseLine.\"VAT Bus. Posting Group\", PurchaseLine.\"VAT Prod. Posting Group\") then\n+ exit(true)\n+ else\n+ exit(false);\n+ end;\n+\n+ local procedure PostNonDeductibleVATEntry(PurchaseLine: Record \"Purchase Line\"; NonDeductibleAmount: Decimal): Boolean\n+ var\n+ GenJournalLine: Record \"Gen. Journal Line\";\n+ PostingSuccess: Boolean;\n+ begin\n+ PostingSuccess := false;\n+\n+ GenJournalLine.Description := 'Non-Deductible VAT - ' + PurchaseLine.\"No.\";\n+\n+ if GenJournalLine.Amount = 0 then begin\n+ begin // Unnecessary BEGIN..END for compound statement\n+ if PostingSuccess then\n+ CreateVATEntry(GenJournalLine, NonDeductibleAmount);\n+ end\n+ else begin\n+ PostingSuccess := false;\n+ end;\n+\n+ exit(PostingSuccess);\n+ end;\n+\n+ local procedure GetNonDeductibleVATAccount(PurchaseLine: Record \"Purchase Line\"; var AccountNo: Code[20]): Boolean\n+ begin\n+ AccountNo := '';\n+ exit(false);\n+ end;\n+\n+ local procedure CreateVATEntry(GenJournalLine: Record \"Gen. Journal Line\"; VATAmount: Decimal)\n+ var\n+ SourceCode: Code[10];\n+ begin\n+ SourceCode := 'PURCHASES';\n+ end;\n+}\ndiff --git a/src/PayablesAgent.Codeunit.al b/src/PayablesAgent.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PayablesAgent.Codeunit.al\n@@ -0,0 +1,61 @@\n+codeunit 50106 \"Payables Agent\"\n+{\n+ Access = Internal;\n+\n+ procedure ProcessSingleInvoice(var PurchaseHeader: Record \"Purchase Header\"): Boolean\n+ var\n+ ProcessingResult: Boolean;\n+ ValidationResult: Boolean;\n+ PostingResult: Boolean;\n+ ErrorMessage: Text;\n+ begin\n+ ProcessingResult := false;\n+\n+ ValidationResult := ValidateInvoice(PurchaseHeader, ErrorMessage);\n+ if not ValidationResult then\n+ exit(false);\n+\n+ if ShouldAutoPost(PurchaseHeader) then begin\n+ begin // BEGIN..END for single statement\n+ if PurchaseHeader.\"Document Type\" = PurchaseHeader.\"Document Type\"::Invoice then\n+ PostingResult := true;\n+ end\n+ else begin\n+ PostingResult := true;\n+ end;\n+\n+ if PostingResult then\n+ ProcessingResult := true;\n+\n+ exit(ProcessingResult);\n+ end;\n+\n+ local procedure ValidateInvoice(var PurchaseHeader: Record \"Purchase Header\"; var ErrorMessage: Text): Boolean\n+ var\n+ PurchaseLine: Record \"Purchase Line\";\n+ Vendor: Record Vendor;\n+ IsValid: Boolean;\n+ begin\n+ IsValid := true;\n+ ErrorMessage := '';\n+\n+ if not Vendor.Get(PurchaseHeader.\"Buy-from Vendor No.\") then begin\n+ ErrorMessage := 'Vendor does not exist: ' + PurchaseHeader.\"Buy-from Vendor No.\";\n+ exit(false);\n+ end;\n+\n+ PurchaseLine.SetRange(\"Document Type\", PurchaseHeader.\"Document Type\");\n+ PurchaseLine.SetRange(\"Document No.\", PurchaseHeader.\"No.\");\n+ if PurchaseLine.IsEmpty() then begin\n+ ErrorMessage := 'No purchase lines found for document: ' + PurchaseHeader.\"No.\";\n+ exit(false);\n+ end;\n+\n+ exit(IsValid);\n+ end;\n+\n+ local procedure ShouldAutoPost(var PurchaseHeader: Record \"Purchase Header\"): Boolean\n+ begin\n+ exit(PurchaseHeader.\"Document Type\" = PurchaseHeader.\"Document Type\"::Invoice);\n+ end;\n+}\ndiff --git a/src/PaymentToleranceManagement.Codeunit.al b/src/PaymentToleranceManagement.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PaymentToleranceManagement.Codeunit.al\n@@ -0,0 +1,61 @@\n+codeunit 50107 \"Payment Tolerance Management\"\n+{\n+ Access = Internal;\n+\n+ var\n+ GeneralLedgerSetup: Record \"General Ledger Setup\";\n+ PaymentTerms: Record \"Payment Terms\";\n+ CurrencyExchangeRate: Record \"Currency Exchange Rate\";\n+\n+ procedure CalculatePaymentTolerance(var CustLedgerEntry: Record \"Cust. Ledger Entry\"; PaymentAmount: Decimal; var ToleranceAmount: Decimal): Boolean\n+ var\n+ RemainingAmount: Decimal;\n+ MaxToleranceAmount: Decimal;\n+ MaxTolerancePercent: Decimal;\n+ IsWithinTolerance: Boolean;\n+ CalculationBase: Decimal;\n+ WorkDate: Date;\n+ PmtToleranceAmount: Decimal;\n+ CurrencyPrecision: Decimal;\n+ begin\n+ ToleranceAmount := 0;\n+ CurrencyPrecision := 0.01;\n+\n+ if CustLedgerEntry.\"Entry No.\" = 0 then\n+ exit(false);\n+\n+ CalculationBase := Abs(RemainingAmount);\n+\n+ if MaxTolerancePercent <> 0 then\n+ ToleranceAmount := Round(CalculationBase * MaxTolerancePercent / 100, CurrencyPrecision);\n+\n+ if (MaxToleranceAmount > 0) and (ToleranceAmount > MaxToleranceAmount) then\n+ ToleranceAmount := MaxToleranceAmount;\n+\n+ IsWithinTolerance := true;\n+\n+ if IsWithinTolerance then begin\n+ if CustLedgerEntry.\"Document Type\" = CustLedgerEntry.\"Document Type\"::Invoice then begin\n+ if (PmtToleranceAmount > 0) and (ToleranceAmount > 0) then\n+ ToleranceAmount := PmtToleranceAmount\n+ else\n+ ToleranceAmount := 0;\n+ end;\n+ end else\n+ ToleranceAmount := 0;\n+\n+ exit(IsWithinTolerance);\n+ end;\n+\n+ procedure PostToleranceEntry(var CustLedgerEntry: Record \"Cust. Ledger Entry\"; ToleranceAmount: Decimal; PostingDate: Date): Boolean\n+ var\n+ GenJournalLine: Record \"Gen. Journal Line\";\n+ PostingResult: Boolean;\n+ begin\n+ PostingResult := false;\n+\n+ GenJournalLine.Description := 'Payment Tolerance for ' + CustLedgerEntry.\"Document No.\";\n+\n+ exit(PostingResult);\n+ end;\n+}\n", "expected_comments": [{"file": "src/AgentTaskTemplate.Codeunit.al", "line_start": 34, "line_end": 34, "body": "The 'begin' keyword is placed after local variable declarations, but there are additional variable declarations after 'begin' in the refactored code structure. The procedure 'ImportTemplate' has its 'begin' keyword at line 34, but there are more variable declarations at lines 44-45 that belong to 'ImportTemplateFromStream'. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/NonDeductiblePurchPosting.Codeunit.al", "line_start": 28, "line_end": 28, "body": "Unnecessary BEGIN..END for compound statement. Per CodeCop AA0005/AA0013, BEGIN should only be used when enclosing multiple statements. The if-then-else structure here uses BEGIN..END appropriately for multiple statements in each branch, but the BEGIN must be on the same line as THEN (which it is). However, the else-begin on line 137 follows the same pattern correctly. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/PayablesAgent.Codeunit.al", "line_start": 19, "line_end": 19, "body": "BEGIN..END used after ELSE on line 81 for a single compound statement that only executes conditionally. The IF on line 78 means there are two statements, so BEGIN..END is correct, but the structure creates potentially unreachable code. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/PaymentToleranceManagement.Codeunit.al", "line_start": 39, "line_end": 39, "body": "Inconsistent indentation - the if statement has excessive indentation that doesn't align with the surrounding code structure — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/AgentTaskTemplate.Codeunit.al", "line_start": 6, "line_end": 6, "body": "Unused global variables (AA0137): TempBlob, TemplateStream, and TemplateOutStream are declared at codeunit scope but never referenced in any procedure. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/AgentTaskTemplate.Codeunit.al", "line_start": 12, "line_end": 12, "body": "Unused variables (AA0137): Multiple variables declared in ImportTemplate (FileManagement, ImportFile, TemplateRecord, TemplateExists, DocumentVersion, DocumentContent, etc.) are never referenced. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/AgentTaskTemplate.Codeunit.al", "line_start": 40, "line_end": 40, "body": "Hardcoded error string with concatenation (AA0217): 'File does not exist: ' is used inline instead of a label variable with proper suffix. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/NonDeductiblePurchPosting.Codeunit.al", "line_start": 8, "line_end": 8, "body": "Unused global variable (AA0137): TempItemLedgerEntry is declared at codeunit scope but never referenced. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/NonDeductiblePurchPosting.Codeunit.al", "line_start": 28, "line_end": 28, "body": "Malformed begin..end..else structure (AA0005): An unnecessary nested begin..end block inside the 'then begin' block creates an invalid else association. — See agent comment for details.", "severity": "critical", "domain": "style"}, {"file": "src/NonDeductiblePurchPosting.Codeunit.al", "line_start": 39, "line_end": 39, "body": "Missing procedure closing end (AA0005): PostNonDeductibleVATEntry procedure is missing its closing 'end;' before a new local procedure begins, resulting in a nested procedure declaration. — See agent comment for details.", "severity": "critical", "domain": "style"}, {"file": "src/NonDeductiblePurchPosting.Codeunit.al", "line_start": 25, "line_end": 25, "body": "Hardcoded string with concatenation (AA0217): 'Non-Deductible VAT - ' is used inline in Description assignment instead of a label variable. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/NonDeductiblePurchPosting.Codeunit.al", "line_start": 49, "line_end": 49, "body": "Hardcoded string 'PURCHASES' should use a label with Tok suffix and Locked = true (AA0217). — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/NonDeductiblePurchPosting.Codeunit.al", "line_start": 12, "line_end": 12, "body": "Simplifiable pattern: 'if X then exit(true) else exit(false)' in GetVATPostingSetup can be simplified to 'exit(X)' for cleaner code. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/PayablesAgent.Codeunit.al", "line_start": 19, "line_end": 19, "body": "Malformed begin..end..else structure (AA0005): An unnecessary nested begin..end block inside the 'then begin' creates an invalid else association. — See agent comment for details.", "severity": "critical", "domain": "style"}, {"file": "src/PayablesAgent.Codeunit.al", "line_start": 33, "line_end": 33, "body": "Missing procedure closing end (AA0005): ProcessSingleInvoice procedure is missing its closing 'end;' before a new local procedure begins. — See agent comment for details.", "severity": "critical", "domain": "style"}, {"file": "src/PayablesAgent.Codeunit.al", "line_start": 43, "line_end": 43, "body": "Hardcoded error string with concatenation (AA0217): 'Vendor does not exist: ' is used inline instead of a label variable. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/PayablesAgent.Codeunit.al", "line_start": 50, "line_end": 50, "body": "Hardcoded error string with concatenation (AA0217): 'No purchase lines found for document: ' is used inline instead of a label variable. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/PaymentToleranceManagement.Codeunit.al", "line_start": 7, "line_end": 7, "body": "Unused global variables (AA0137): PaymentTerms and CurrencyExchangeRate are declared but never referenced. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/PaymentToleranceManagement.Codeunit.al", "line_start": 17, "line_end": 17, "body": "Variable name conflict (AA0204): Local variable 'WorkDate' shadows the built-in WorkDate() function. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/PaymentToleranceManagement.Codeunit.al", "line_start": 57, "line_end": 57, "body": "Hardcoded string with concatenation (AA0217): 'Payment Tolerance for ' is used inline in Description instead of a label variable. — See agent comment for details.", "severity": "high", "domain": "style"}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "True positive style findings: code_structure (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-012", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/CustomerNotification.Codeunit.al b/src/CustomerNotification.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerNotification.Codeunit.al\n@@ -0,0 +1,18 @@\n+codeunit 50301 \"Customer Notification\"\n+{\n+ Access = Public;\n+\n+ procedure SendOverdueNotice(CustomerNo: Code[20])\n+ begin\n+ // SendEmail(Customer);\n+ Message('An overdue notice has been sent to the customer.');\n+ end;\n+\n+ procedure MarkAsNotified(EntryNo: Integer)\n+ begin\n+ if GuiAllowed() then\n+ if not Confirm('Are you sure you want to mark entry as notified?') then\n+ exit;\n+ Message('Entry has been marked as notified.');\n+ end;\n+}\ndiff --git a/src/InventoryHelper.Codeunit.al b/src/InventoryHelper.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/InventoryHelper.Codeunit.al\n@@ -0,0 +1,27 @@\n+codeunit 50300 \"Inventory Helper\"\n+{\n+ Access = Public;\n+\n+\n+ var\n+ UnusedCounter: Integer;\n+\n+ procedure AdjustStock(ItemNo: Code[20]; Qty: Decimal): Decimal\n+ var\n+ Item: Record Item;\n+ begin\n+ if Item.Get(ItemNo) then\n+ exit(Item.\"Reorder Quantity\" + Qty);\n+ exit(0);\n+ end;\n+\n+ procedure PostAdjustment(ItemNo: Code[20]; Qty: Decimal)\n+ var\n+ TempValue: Decimal;\n+ begin\n+ if Qty = 0 then\n+ Error('Quantity must not be zero.');\n+\n+ Message('Adjustment posted for item ' + ItemNo);\n+ end;\n+}\ndiff --git a/src/PurchaseValidator.Codeunit.al b/src/PurchaseValidator.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PurchaseValidator.Codeunit.al\n@@ -0,0 +1,17 @@\n+codeunit 50302 \"Purchase Validator\"\n+{\n+ Access = Public;\n+\n+ procedure ValidateHeader(PurchaseHeader: Record \"Purchase Header\"): Boolean\n+ begin\n+ if PurchaseHeader.\"Buy-from Vendor No.\" = '' then\n+ Error('Vendor must be specified.');\n+ exit(true);\n+ end;\n+\n+ procedure ValidateLines(DocNo: Code[20])\n+ begin\n+ if DocNo = '' then\n+ Error('Purchase document must have at least one line.');\n+ end;\n+}\n", "expected_comments": [{"file": "src/InventoryHelper.Codeunit.al", "line_start": 9, "line_end": 9, "body": "Public procedure 'AdjustStock' lacks XML documentation comments. Public procedures should have /// documentation. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/InventoryHelper.Codeunit.al", "line_start": 7, "line_end": 7, "body": "Unused global variable 'UnusedCounter' (AA0137). — See agent comment for details.", "severity": "low", "domain": "style"}, {"file": "src/InventoryHelper.Codeunit.al", "line_start": 23, "line_end": 23, "body": "Hardcoded text string in Error() call (CodeCop AA0217): 'Quantity must not be zero.' should use a Label variable. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/InventoryHelper.Codeunit.al", "line_start": 25, "line_end": 25, "body": "Hardcoded text string in Message() call (CodeCop AA0217): 'Adjustment posted for item ' should use a Label variable. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/CustomerNotification.Codeunit.al", "line_start": 7, "line_end": 7, "body": "Commented-out code '// SendEmail(Customer);' should be removed (clean code). — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/CustomerNotification.Codeunit.al", "line_start": 8, "line_end": 8, "body": "Hardcoded text string in Message() call (CodeCop AA0217): 'Overdue notice sent to customer ' should use a Label variable. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/CustomerNotification.Codeunit.al", "line_start": 14, "line_end": 14, "body": "Hardcoded text string in Confirm() call (CodeCop AA0217): 'Are you sure you want to mark entry as notified?' should use a Label variable with Qst suffix. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/PurchaseValidator.Codeunit.al", "line_start": 8, "line_end": 8, "body": "Hardcoded text string in Error() call (CodeCop AA0217): 'Vendor must be specified.' should use a Label variable with Err suffix. — See agent comment for details.", "severity": "high", "domain": "style"}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "True positive style findings: documentation — missing XML docs, hardcoded strings, unused variables, commented-out code", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-013", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/CloudMigReplicateDataMgt.Codeunit.al b/src/CloudMigReplicateDataMgt.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CloudMigReplicateDataMgt.Codeunit.al\n@@ -0,0 +1,18 @@\n+codeunit 50117 \"Cloud Mig. Replicate Data Mgt\"\n+{\n+ Access = Internal;\n+\n+ var\n+ TablesCannotBeEnabledForReplicationErr: Label 'The following tables cannot be enabled for replication because they contain sensitive data or are system tables:%1\\\\Please review the table selection and remove any tables that should not be replicated to ensure data security and compliance with privacy regulations.', Comment = '%1 = List of table names';\n+\n+ procedure ValidateTablesForReplication(var TableList: Record \"Cloud Migration Table\" temporary; RestrictedTableNames: Text)\n+ begin\n+ if RestrictedTableNames <> '' then\n+ Error(TablesCannotBeEnabledForReplicationErr, RestrictedTableNames);\n+ end;\n+\n+ procedure ReportReplicationResult(EnabledCount: Integer; TableCount: Integer)\n+ begin\n+ Message('Replication enabled for %1 of %2 tables', EnabledCount, TableCount);\n+ end;\n+}\ndiff --git a/src/CustStPDFDocHandler.Codeunit.al b/src/CustStPDFDocHandler.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustStPDFDocHandler.Codeunit.al\n@@ -0,0 +1,62 @@\n+codeunit 50114 \"Cust. St. PDF Doc Handler\"\n+{\n+ Access = Internal;\n+\n+ var\n+ TempBlob: Codeunit \"Temp Blob\";\n+ FileManagement: Codeunit \"File Management\";\n+\n+ UnableToProcessDocumentErr: Label 'Unable to process document with SystemId %1,', Comment = 'SystemId %1';\n+\n+ procedure ProcessCustomerStatement(CustomerNo: Code[20]; StatementDate: Date): Boolean\n+ var\n+ ProcessingResult: Boolean;\n+ StatementGuid: Guid;\n+ begin\n+ ProcessingResult := false;\n+\n+ if IsNullGuid(StatementGuid) then\n+ Error(UnableToProcessDocumentErr, StatementGuid)\n+ else\n+ Error('Unable to create customer statement record for customer %1', CustomerNo);\n+\n+ exit(ProcessingResult);\n+ end;\n+\n+ local procedure GenerateStatementReport(ReportID: Integer; OutputFileName: Text): Boolean\n+ var\n+ GenerationSuccess: Boolean;\n+ begin\n+ GenerationSuccess := false;\n+\n+ try\n+ GenerationSuccess := FileManagement.ServerFileExists(OutputFileName);\n+ GenerationSuccess := FileManagement.ServerFileExists(OutputFileName);\n+ except\n+ GenerationSuccess := false;\n+ end;\n+\n+ exit(GenerationSuccess);\n+ end;\n+\n+ procedure ValidateStatementParameters(CustomerNo: Code[20]; StatementDate: Date): Boolean\n+ var\n+ Customer: Record Customer;\n+ ValidationResult: Boolean;\n+ begin\n+ ValidationResult := true;\n+\n+ Customer.SetLoadFields(\"No.\");\n+ if not Customer.Get(CustomerNo) then begin\n+ Error('Customer %1 does not exist', CustomerNo);\n+ ValidationResult := false;\n+ end;\n+\n+ if StatementDate > Today then begin\n+ Error('Statement date cannot be in the future');\n+ ValidationResult := false;\n+ end;\n+\n+ exit(ValidationResult);\n+ end;\n+}\ndiff --git a/src/ExpenseCategory.Table.al b/src/ExpenseCategory.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExpenseCategory.Table.al\n@@ -0,0 +1,155 @@\n+table 50115 \"Expense Category\"\n+{\n+ Caption = 'Expense Category';\n+ DataCaptionFields = Code, Description;\n+ DrillDownPageId = \"Expense Categories\";\n+ LookupPageId = \"Expense Categories\";\n+\n+ fields\n+ {\n+ field(1; Code; Code[20])\n+ {\n+ Caption = 'Code';\n+ NotBlank = true;\n+ DataClassification = CustomerContent;\n+ }\n+ field(2; Description; Text[100])\n+ {\n+ Caption = 'Description';\n+ DataClassification = CustomerContent;\n+ }\n+ field(3; \"G/L Account No.\"; Code[20])\n+ {\n+ Caption = 'G/L Account No.';\n+ TableRelation = \"G/L Account\" where(\"Account Type\" = const(Posting),\n+ Blocked = const(false));\n+ DataClassification = CustomerContent;\n+\n+ trigger OnValidate()\n+ begin\n+ if \"G/L Account No.\" <> xRec.\"G/L Account No.\" then\n+ ValidateGLAccount();\n+ end;\n+ }\n+ field(4; \"Expense Type\"; Enum \"Expense Type\")\n+ {\n+ Caption = 'Expense Type';\n+ DataClassification = CustomerContent;\n+ }\n+ field(5; \"Requires Receipt\"; Boolean)\n+ {\n+ Caption = 'Requires Receipt';\n+ DataClassification = CustomerContent;\n+ InitValue = true;\n+ }\n+ field(6; \"Max Amount\"; Decimal)\n+ {\n+ Caption = 'Max Amount';\n+ DataClassification = CustomerContent;\n+ MinValue = 0;\n+\n+ trigger OnValidate()\n+ begin\n+ if \"Max Amount\" < 0 then\n+ Error('Maximum amount cannot be negative');\n+ end;\n+ }\n+ field(7; \"Approval Required\"; Boolean)\n+ {\n+ Caption = 'Approval Required';\n+ DataClassification = CustomerContent;\n+ }\n+ field(8; \"Approval Amount Threshold\"; Decimal)\n+ {\n+ Caption = 'Approval Amount Threshold';\n+ DataClassification = CustomerContent;\n+ MinValue = 0;\n+ }\n+ field(40; \"Active\"; Boolean)\n+ {\n+ Caption = 'Active';\n+ DataClassification = CustomerContent;\n+ InitValue = true;\n+ }\n+ field(41; \"Effective Date\"; Date)\n+ {\n+ Caption = 'Effective Date';\n+ DataClassification = CustomerContent;\n+ }\n+ field(42; \"Expiration Date\"; Date)\n+ {\n+ Caption = 'Expiration Date';\n+ DataClassification = CustomerContent;\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; Code)\n+ {\n+ Clustered = true;\n+ }\n+ }\n+\n+ fieldgroups\n+ {\n+ fieldgroup(DropDown; Code, Description, \"Expense Type\")\n+ {\n+ }\n+ fieldgroup(Brick; Code, Description, \"G/L Account No.\", Active)\n+ {\n+ }\n+ }\n+\n+ trigger OnInsert()\n+ begin\n+ if \"Effective Date\" = 0D then\n+ \"Effective Date\" := Today;\n+\n+ ValidateExpenseCategory();\n+ end;\n+\n+ trigger OnModify()\n+ begin\n+ ValidateExpenseCategory();\n+ end;\n+\n+ trigger OnDelete()\n+ var\n+ ExpenseLine: Record \"Expense Line\";\n+ begin\n+ ExpenseLine.SetRange(\"Expense Category Code\", Code);\n+ if not ExpenseLine.IsEmpty() then\n+ Error('Cannot delete expense category %1 because it is used in expense lines', Code);\n+ end;\n+\n+ local procedure ValidateGLAccount()\n+ var\n+ GLAccount: Record \"G/L Account\";\n+ begin\n+ if \"G/L Account No.\" = '' then\n+ exit;\n+\n+ if not GLAccount.Get(\"G/L Account No.\") then\n+ Error('G/L Account %1 does not exist', \"G/L Account No.\");\n+\n+ if GLAccount.\"Account Type\" <> GLAccount.\"Account Type\"::Posting then\n+ Error('G/L Account %1 must be a posting account', \"G/L Account No.\");\n+\n+ if GLAccount.Blocked then\n+ Error('G/L Account %1 is blocked', \"G/L Account No.\");\n+ end;\n+\n+ local procedure ValidateExpenseCategory()\n+ begin\n+ if Code = '' then\n+ Error('');\n+\n+ if Description = '' then\n+ Error('Description must be specified');\n+\n+ if (\"Effective Date\" <> 0D) and (\"Expiration Date\" <> 0D) then\n+ if \"Expiration Date\" < \"Effective Date\" then\n+ Error('Expiration date cannot be before effective date');\n+ end;\n+}\ndiff --git a/src/ItemCategoryAttributes.Page.al b/src/ItemCategoryAttributes.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ItemCategoryAttributes.Page.al\n@@ -0,0 +1,103 @@\n+page 50118 \"Item Category Attributes\"\n+{\n+ ApplicationArea = Basic, Suite;\n+ Caption = 'Item Category Attributes';\n+ PageType = List;\n+ SourceTable = \"Item Attribute\";\n+ UsageCategory = Lists;\n+\n+ layout\n+ {\n+ area(content)\n+ {\n+ repeater(Control1)\n+ {\n+ ShowCaption = false;\n+ field(Name; Rec.Name)\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the descriptive name that identifies this attribute when classifying items in the catalog.';\n+ }\n+ field(Type; Rec.Type)\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the data type that determines how values are entered and validated for this attribute.';\n+ }\n+ field(Values; Rec.Values)\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the predefined list of option values that users can select from for this attribute.';\n+ Visible = Rec.Type = Rec.Type::Option;\n+ }\n+ field(\"Unit of Measure\"; Rec.\"Unit of Measure\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the unit of measure applied when this attribute stores numeric measurements.';\n+ Visible = Rec.Type = Rec.Type::Decimal;\n+ }\n+ field(Blocked; Rec.Blocked)\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies that the attribute is excluded from selection and can no longer be assigned to items.';\n+ }\n+ }\n+ }\n+ }\n+\n+ actions\n+ {\n+ area(processing)\n+ {\n+ action(\"Import Attributes\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ Caption = 'Import Attributes';\n+ Image = Import;\n+ ToolTip = 'Import item attributes from an external CSV file into the catalog.';\n+\n+ trigger OnAction()\n+ var\n+ ImportManager: Codeunit \"Attribute Import Manager\";\n+ FilePath: Text;\n+ ImportResult: Boolean;\n+ begin\n+ if not UploadIntoStream('Select attribute file', '', 'CSV Files|*.csv', FilePath, ImportStream) then\n+ exit;\n+\n+ ImportResult := ImportManager.ImportFromCSV(ImportStream);\n+ if ImportResult then\n+ Message('Attributes imported successfully.')\n+ else\n+ Message('Import completed with errors. Please review the results.');\n+ end;\n+ }\n+ }\n+ }\n+\n+ trigger OnOpenPage()\n+ var\n+ FeatureManagement: Codeunit \"Feature Management\";\n+ BlankOptionAttributeNotification: Notification;\n+ begin\n+ if not FeatureManagement.IsEnabled('ItemAttributes') then begin\n+ Message('Item attributes feature is not enabled. Please contact your system administrator.');\n+ CurrPage.Close();\n+ exit;\n+ end;\n+\n+ BlankOptionAttributeNotification.Id := CreateGuid();\n+ BlankOptionAttributeNotification.Message := BlankOptionAttributeNotificationMsg;\n+ BlankOptionAttributeNotification.Scope := NotificationScope::LocalScope;\n+ BlankOptionAttributeNotification.Send();\n+ end;\n+\n+ trigger OnNewRecord(BelowxRec: Boolean)\n+ begin\n+ Rec.Type := Rec.Type::Text;\n+ Rec.Blocked := false;\n+ end;\n+\n+ var\n+ ImportStream: InStream;\n+ BlankOptionAttributeNotificationMsg: Label 'Some option attributes have blank values that may cause issues.';\n+}\ndiff --git a/src/SCMSupplyPlanningIV.Codeunit.al b/src/SCMSupplyPlanningIV.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/SCMSupplyPlanningIV.Codeunit.al\n@@ -0,0 +1,42 @@\n+codeunit 50116 \"SCM Supply Planning IV\"\n+{\n+ Subtype = Test;\n+\n+ var\n+ Assert: Codeunit Assert;\n+ LibraryInventory: Codeunit \"Library - Inventory\";\n+\n+ AssemblyOrderCreatedMsg: Label 'Assembly order %1 has been created successfully.', Comment = '%1 = Order No.';\n+\n+ [Test]\n+ procedure TestCreateAssemblyOrder()\n+ var\n+ Item: Record Item;\n+ AssemblyHeader: Record \"Assembly Header\";\n+ OrderNo: Code[20];\n+ begin\n+ LibraryInventory.CreateItem(Item);\n+ Item.\"Assembly Policy\" := Item.\"Assembly Policy\"::\"Assemble-to-Order\";\n+ Item.Modify(true);\n+\n+ OrderNo := CreateAssemblyOrderForItem(Item.\"No.\", 10);\n+\n+ AssemblyHeader.Get(AssemblyHeader.\"Document Type\"::Order, OrderNo);\n+ Assert.AreEqual(Item.\"No.\", AssemblyHeader.\"Item No.\", 'Item number should match');\n+ Assert.AreEqual(10, AssemblyHeader.Quantity, AssemblyOrderCreatedMsg);\n+ end;\n+\n+ local procedure CreateAssemblyOrderForItem(ItemNo: Code[20]; Quantity: Decimal): Code[20]\n+ var\n+ AssemblyHeader: Record \"Assembly Header\";\n+ begin\n+ AssemblyHeader.Init();\n+ AssemblyHeader.\"Document Type\" := AssemblyHeader.\"Document Type\"::Order;\n+ AssemblyHeader.\"No.\" := CopyStr(Format(CreateGuid()), 1, 20);\n+ AssemblyHeader.\"Item No.\" := ItemNo;\n+ AssemblyHeader.Quantity := Quantity;\n+ AssemblyHeader.\"Due Date\" := CalcDate('<+7D>', Today);\n+ AssemblyHeader.Insert(true);\n+ exit(AssemblyHeader.\"No.\");\n+ end;\n+}\n", "expected_comments": [{"file": "src/CloudMigReplicateDataMgt.Codeunit.al", "line_start": 11, "line_end": 11, "domain": "style", "body": "Label suffix inconsistency: 'TablesCannotBeEnabledForReplicationErr' uses 'Err' suffix but the message text is more informational/instructional than a pure error condition. However, since it's used with Error(), the suffix is technically correct. — See agent comment for details.", "severity": "medium"}, {"file": "src/CloudMigReplicateDataMgt.Codeunit.al", "line_start": 16, "line_end": 16, "domain": "style", "body": "Hardcoded text string in Message() call (CodeCop AA0217): 'Replication enabled for %1 of %2 tables' should use a Label with Msg suffix. — See agent comment for details.", "severity": "medium"}, {"file": "src/SCMSupplyPlanningIV.Codeunit.al", "line_start": 26, "line_end": 26, "domain": "style", "body": "Label variable 'AssemblyOrderCreatedMsg' uses 'Msg' suffix but is used as an assertion failure message, not a user-facing Message() call. The suffix should indicate the actual usage context. — See agent comment for details.", "severity": "medium"}, {"file": "src/ExpenseCategory.Table.al", "line_start": 54, "line_end": 54, "domain": "style", "body": "Using Error('') with empty string is not recommended (CodeCop AA0216). Error messages should use label variables with proper suffix. — See agent comment for details.", "severity": "medium"}, {"file": "src/ExpenseCategory.Table.al", "line_start": 123, "line_end": 123, "domain": "style", "body": "Hardcoded error message string in Error() call: 'Maximum amount cannot be negative' (CodeCop AA0217). — See agent comment for details.", "severity": "medium"}, {"file": "src/ExpenseCategory.Table.al", "line_start": 134, "line_end": 134, "domain": "style", "body": "Hardcoded error message string in Error() call: 'Cannot delete expense category %1 because it is used in expense lines' (CodeCop AA0217). — See agent comment for details.", "severity": "medium"}, {"file": "src/ExpenseCategory.Table.al", "line_start": 146, "line_end": 146, "domain": "style", "body": "Hardcoded error message strings in Error() calls in ValidateGLAccount (CodeCop AA0217). — See agent comment for details.", "severity": "medium"}, {"file": "src/ExpenseCategory.Table.al", "line_start": 149, "line_end": 149, "domain": "style", "body": "Hardcoded error message strings in Error() calls in ValidateExpenseCategory (CodeCop AA0217). — See agent comment for details.", "severity": "medium"}, {"file": "src/ItemCategoryAttributes.Page.al", "line_start": 69, "line_end": 69, "domain": "style", "body": "Label variable 'BlankOptionAttributeNotificationMsg' uses 'Msg' suffix but it's used for a notification, not a Message() dialog. Per AA0074, 'Msg' suffix is for Message() calls. — See agent comment for details.", "severity": "medium"}, {"file": "src/ItemCategoryAttributes.Page.al", "line_start": 91, "line_end": 91, "domain": "style", "body": "Hardcoded message strings in Message() calls (CodeCop AA0217). Multiple Message() calls use inline strings instead of Label variables. — See agent comment for details.", "severity": "medium"}, {"file": "src/ItemCategoryAttributes.Page.al", "line_start": 102, "line_end": 102, "domain": "style", "body": "Label variable BlankOptionAttributeNotificationMsg is declared but the hardcoded string is used directly on line 165 instead of using the label. — See agent comment for details.", "severity": "low"}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "True positive style findings: error_handling (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-014", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/ReportFormatting.Codeunit.al b/src/ReportFormatting.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ReportFormatting.Codeunit.al\n@@ -0,0 +1,29 @@\n+codeunit 50302 \"Report Formatting\"\n+{\n+ local procedure RunBatchProcess()\n+ var\n+ Customer: Record Customer;\n+ ProcessedCount: Integer;\n+ begin\n+ Customer.SetLoadFields(\"No.\", Blocked);\n+ if not Customer.FindSet() then\n+ Error('No customers found for processing.');\n+\n+ repeat\n+ ProcessedCount += 1;\n+ if Customer.Blocked <> Customer.Blocked::\" \" then\n+ Error('Customer %1 is blocked and cannot be processed.', Customer.\"No.\");\n+ until Customer.Next() = 0;\n+\n+ if Confirm('Do you want to see the processing summary?') then\n+ Message('Successfully processed %1 customers.', ProcessedCount);\n+ end;\n+\n+ local procedure ValidateSetup()\n+ var\n+ ServiceSetup: Record \"Service Mgt. Setup\";\n+ begin\n+ if not ServiceSetup.Get() then\n+ Error('Service Management Setup has not been configured.');\n+ end;\n+}\n", "expected_comments": [{"file": "src/ReportFormatting.Codeunit.al", "line_start": 10, "line_end": 10, "body": "Hardcoded error string 'No customers found for processing.' in Error() call instead of using a Label variable with Err suffix (CodeCop AA0217). — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/ReportFormatting.Codeunit.al", "line_start": 15, "line_end": 15, "body": "Hardcoded error string 'Customer %1 is blocked and cannot be processed.' in Error() call instead of using a Label variable with Err suffix (CodeCop AA0217). — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/ReportFormatting.Codeunit.al", "line_start": 18, "line_end": 18, "body": "Hardcoded confirm string 'Do you want to see the processing summary?' in Confirm() call instead of using a Label variable with Qst suffix (CodeCop AA0217). — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/ReportFormatting.Codeunit.al", "line_start": 19, "line_end": 19, "body": "Hardcoded message string 'Successfully processed %1 customers.' in Message() call instead of using a Label variable with Msg suffix (CodeCop AA0217). — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/ReportFormatting.Codeunit.al", "line_start": 27, "line_end": 27, "body": "Hardcoded error string 'Service Management Setup has not been configured.' in Error() call instead of using a Label variable with Err suffix (CodeCop AA0217). — See agent comment for details.", "severity": "high", "domain": "style"}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "True positive style findings: hardcoded strings in formatting violations", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-015", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/NamingViolations.Codeunit.al b/src/NamingViolations.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/NamingViolations.Codeunit.al\n@@ -0,0 +1,20 @@\n+codeunit 50303 \"Naming Issues Demo\"\n+{\n+ procedure process_customer(cust_no: Code[20]): Boolean\n+ var\n+ x: Record Customer;\n+ begin\n+ if x.Get(cust_no) then begin\n+ this.update_record(x);\n+ exit(true);\n+ end;\n+ exit(false);\n+ end;\n+\n+ local procedure update_record(var Cust: Record Customer)\n+ begin\n+ Cust.TestField(Name);\n+ Cust.Modify(true);\n+ end;\n+}\n+\n", "expected_comments": [{"file": "src/NamingViolations.Codeunit.al", "line_start": 3, "line_end": 3, "body": "Procedure name 'process_customer' uses snake_case instead of PascalCase. AL naming conventions require PascalCase for all procedure names. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/NamingViolations.Codeunit.al", "line_start": 3, "line_end": 3, "body": "Parameter 'cust_no' uses snake_case instead of PascalCase. AL naming conventions require PascalCase for all parameter names. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/NamingViolations.Codeunit.al", "line_start": 5, "line_end": 5, "body": "Non-descriptive variable name 'x'. Variable names should be meaningful and describe the data they hold (e.g., 'Customer' instead of 'x'). — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/NamingViolations.Codeunit.al", "line_start": 14, "line_end": 14, "body": "Procedure name 'update_record' uses snake_case instead of PascalCase. AL naming conventions require PascalCase for all procedure names. — See agent comment for details.", "severity": "high", "domain": "style"}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "True positive style findings: obvious naming convention violations", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-016", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/PriceCalculationHandler.Enum.al b/src/PriceCalculationHandler.Enum.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PriceCalculationHandler.Enum.al\n@@ -0,0 +1,41 @@\n+/// \n+/// Enum for price calculation handlers\n+/// \n+enum 50125 \"Price Calculation Handler\"\n+{\n+ Extensible = true;\n+\n+ value(0; \"Business Central (Version 16.0)\")\n+ {\n+ Caption = 'Business Central (Version 16.0)';\n+ Implementation = \"Price Calculation\" = \"Price Calculation - V16\";\n+ ToolTip = 'Uses the standard Business Central price calculation from version 16.0.';\n+ }\n+ value(1; \"Business Central (Version 15.0)\")\n+ {\n+ Caption = 'Business Central (Version 15.0)';\n+ Implementation = \"Price Calculation\" = \"Price Calculation - V15\";\n+ ToolTip = 'Uses the legacy Business Central price calculation from version 15.0.';\n+ ObsoleteState = Pending;\n+ ObsoleteReason = 'Replaced by the new price calculation engine introduced in Business Central 2021 Wave 1.';\n+ ObsoleteTag = '16.0';\n+ }\n+ value(2; \"Custom Price Engine\")\n+ {\n+ Caption = 'Custom Price Engine';\n+ Implementation = \"Price Calculation\" = \"Price Calculation - Custom\";\n+ ToolTip = 'Uses a custom price calculation engine with extended features.';\n+ }\n+ value(3; \"External Price Service\")\n+ {\n+ Caption = 'External Price Service';\n+ Implementation = \"Price Calculation\" = \"Price Calculation - External\";\n+ ToolTip = 'Integrates with external pricing services for dynamic pricing.';\n+ }\n+ value(4; \"AI-Powered Pricing\")\n+ {\n+ Caption = 'AI-Powered Pricing';\n+ Implementation = \"Price Calculation\" = \"Price Calculation - AI\";\n+ ToolTip = 'Uses artificial intelligence to calculate optimal pricing based on market conditions.';\n+ }\n+}\ndiff --git a/src/ReqWorksheetTemplateType.Enum.al b/src/ReqWorksheetTemplateType.Enum.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ReqWorksheetTemplateType.Enum.al\n@@ -0,0 +1,38 @@\n+/// \n+/// Enum for requisition worksheet template types\n+/// \n+enum 50124 \"Req. Worksheet Template Type\"\n+{\n+ Extensible = true;\n+\n+ value(0; \"Req.\")\n+ {\n+ Caption = 'Req.';\n+ ToolTip = 'Standard requisition worksheet for planning purchases and production.';\n+ }\n+ value(1; \"For. Labor\")\n+ {\n+ Caption = 'For. Labor';\n+ ToolTip = 'Foreign labor requisition worksheet for specialized workforce planning.';\n+ }\n+ value(2; Planning)\n+ {\n+ Caption = 'Planning';\n+ ToolTip = 'Planning worksheet for MRP calculations and supply planning.';\n+#if not CLEAN28 // Inconsistent preprocessor placement - line 17\n+ ObsoleteState = Pending;\n+ ObsoleteReason = 'This template type will be replaced by the new Planning Engine in version 28.0';\n+ ObsoleteTag = '28.0';\n+#endif\n+ }\n+ value(3; \"Subcontracting\")\n+ {\n+ Caption = 'Subcontracting';\n+ ToolTip = 'Subcontracting worksheet for managing outsourced production operations.';\n+ }\n+ value(4; \"Service\")\n+ {\n+ Caption = 'Service';\n+ ToolTip = 'Service requisition worksheet for service item requirements.';\n+ }\n+}\ndiff --git a/src/WHTPstdPurchTaxCrMemos.Page.al b/src/WHTPstdPurchTaxCrMemos.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/WHTPstdPurchTaxCrMemos.Page.al\n@@ -0,0 +1,155 @@\n+/// \n+/// Page for WHT Posted Purchase Tax Credit Memos\n+/// \n+page 50126 \"WHT Pstd. Purch. Tax Cr. Memos\"\n+{\n+ ApplicationArea = Basic, Suite;\n+ Caption = 'WHT Posted Purchase Tax Credit Memos';\n+ CardPageID = \"WHT Posted Purch. Tax Cr. Memo\";\n+ DeleteAllowed = false;\n+ Editable = false;\n+ InsertAllowed = false;\n+ ModifyAllowed = false;\n+ PageType = List;\n+ SourceTable = \"WHT Posted Purch. Tax Cr. Memo\";\n+ UsageCategory = History;\n+\n+\n+ layout\n+ {\n+ area(content)\n+ {\n+ repeater(Control1)\n+ {\n+ ShowCaption = false;\n+ field(\"No.\"; Rec.\"No.\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the number of the posted purchase tax credit memo.';\n+ }\n+ field(\"Buy-from Vendor No.\"; Rec.\"Buy-from Vendor No.\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the vendor from whom the credit memo was received.';\n+ }\n+ field(\"Buy-from Vendor Name\"; Rec.\"Buy-from Vendor Name\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the name of the vendor from whom the credit memo was received.';\n+ }\n+ field(\"Posting Date\"; Rec.\"Posting Date\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the date when the credit memo was posted.';\n+ }\n+ field(\"Document Date\"; Rec.\"Document Date\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the date of the original document.';\n+ }\n+ field(\"Amount Including VAT\"; Rec.\"Amount Including VAT\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the total amount of the credit memo including VAT.';\n+ }\n+ field(\"WHT Amount\"; Rec.\"WHT Amount\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the withholding tax amount on the credit memo.';\n+ }\n+ field(\"Currency Code\"; Rec.\"Currency Code\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ ToolTip = 'Specifies the currency code of the credit memo.';\n+ }\n+ }\n+ }\n+ area(factboxes)\n+ {\n+ part(IncomingDocAttachFactBox; \"Incoming Doc. Attach. FactBox\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ SubPageLink = \"Document No.\" = field(\"No.\"),\n+ \"Posting Date\" = field(\"Posting Date\");\n+ }\n+ systempart(Control1900383207; Links)\n+ {\n+ ApplicationArea = RecordLinks;\n+ Visible = false;\n+ }\n+ systempart(Control1905767507; Notes)\n+ {\n+ ApplicationArea = Notes;\n+ Visible = false;\n+ }\n+ }\n+ }\n+\n+ actions\n+ {\n+ area(processing)\n+ {\n+ action(\"Print Credit Memo\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ Caption = 'Print Credit Memo';\n+ Image = Print;\n+ ToolTip = 'Print the selected credit memo document.';\n+\n+ trigger OnAction()\n+ begin\n+ CurrPage.SetSelectionFilter(Rec);\n+ Report.RunModal(Report::\"WHT Purchase Tax Credit Memo\", true, true, Rec);\n+ end;\n+ }\n+ action(\"Email Credit Memo\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ Caption = 'Email Credit Memo';\n+ Image = Email;\n+ ToolTip = 'Email the selected credit memo document.';\n+\n+ trigger OnAction()\n+ var\n+ EmailManagement: Codeunit \"Email Management\";\n+ DocumentSendingProfile: Record \"Document Sending Profile\";\n+ begin\n+ DocumentSendingProfile.SendVendorRecords(\n+ Report::\"WHT Purchase Tax Credit Memo\", Rec, 'Credit Memo', Rec.\"Buy-from Vendor No.\",\n+ Rec.\"No.\", Rec.FieldNo(\"Buy-from Vendor No.\"), Rec.FieldNo(\"No.\"));\n+ end;\n+ }\n+ }\n+ area(navigation)\n+ {\n+ group(\"Related Information\")\n+ {\n+ Caption = 'Related Information';\n+ action(\"WHT Certificate\")\n+ {\n+ ApplicationArea = Basic, Suite;\n+ Caption = 'WHT Certificate';\n+ Image = Certificate;\n+ RunObject = Page \"WHT Certificate\";\n+ RunPageLink = \"Document No.\" = field(\"No.\"),\n+ \"Document Type\" = const(\"Credit Memo\");\n+ ToolTip = 'View the withholding tax certificate associated with this credit memo.';\n+ }\n+ }\n+ }\n+ }\n+\n+ trigger OnOpenPage()\n+ begin\n+ Error('This page has been marked as obsolete for the Withholding Tax app and is no longer supported.');\n+ end;\n+\n+ trigger OnAfterGetRecord()\n+ begin\n+ // Additional processing could be added here if needed\n+ // This trigger is maintained for compatibility during the transition period\n+ end;\n+\n+ var\n+ ObsoletePageUsedErr: Label 'This page has been marked as obsolete for the Withholding Tax app and is no longer supported. Please use the new Withholding Tax Posted Credit Memo List page instead.';\n+}\n", "expected_comments": [{"file": "src/PriceCalculationHandler.Enum.al", "line_start": 21, "line_end": 21, "body": "ObsoleteTag value '16.0' appears incorrect. The ObsoleteTag should match the version where the obsoletion was introduced (likely '27.0' based on the CLEAN27 preprocessor directive), not the version the implementation refers to. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/ReqWorksheetTemplateType.Enum.al", "line_start": 22, "line_end": 22, "body": "Inconsistent preprocessor directive placement: The '#if not CLEAN28' is inside the enum value definition which is unusual. The ObsoleteState should use #else to also set ObsoleteState = Removed when CLEAN28 is defined — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/WHTPstdPurchTaxCrMemos.Page.al", "line_start": 144, "line_end": 144, "body": "Error message uses hardcoded string instead of a label variable (CodeCop AA0216, AA0217). The Error() call uses inline text 'This page has been marked as obsolete for the Withholding Tax app and is no longer supported.' instead of a properly declared Label with an 'Err' suffix. — See agent comment for details.", "severity": "high", "domain": "style"}, {"file": "src/WHTPstdPurchTaxCrMemos.Page.al", "line_start": 114, "line_end": 114, "body": "Unused variable 'EmailManagement' declared but never referenced in the trigger body (CodeCop AA0137). — See agent comment for details.", "severity": "medium", "domain": "style"}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "True positive style findings: obsolete (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__style-017", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "style"}, "patch": "diff --git a/src/ExpenseTeams.Page.al b/src/ExpenseTeams.Page.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExpenseTeams.Page.al\n@@ -0,0 +1,182 @@\n+page 50127 \"Expense Teams\"\n+{\n+ ApplicationArea = All;\n+ Caption = 'Expense Teams';\n+ PageType = List;\n+ SourceTable = \"Expense Team\";\n+ UsageCategory = Lists;\n+ AdditionalSearchTerms = 'team,group,expense management,approval,workflow';\n+\n+ layout\n+ {\n+ area(Content)\n+ {\n+ repeater(GroupName)\n+ {\n+ field(Code; Rec.Code)\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the unique code that identifies the expense team.';\n+ }\n+ field(Name; Rec.Name)\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the name of the expense team.';\n+ }\n+ field(Description; Rec.Description)\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies a description of the expense team and its purpose.';\n+ }\n+ field(\"Team Leader\"; Rec.\"Team Leader\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the user who leads this expense team.';\n+ }\n+ field(\"Default Approver\"; Rec.\"Default Approver\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the default approver for expenses submitted by team members.';\n+ }\n+ field(\"Max Approval Amount\"; Rec.\"Max Approval Amount\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the maximum amount that can be approved by the team leader.';\n+ }\n+ field(Active; Rec.Active)\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies whether the expense team is active and can be used for expense processing.';\n+ }\n+ }\n+ }\n+ area(Factboxes)\n+ {\n+ part(TeamMembersFactBox; \"Expense Team Members FactBox\")\n+ {\n+ ApplicationArea = All;\n+ SubPageLink = \"Team Code\" = field(Code);\n+ }\n+ }\n+ }\n+\n+ actions\n+ {\n+ area(Processing)\n+ {\n+ action(EditTeamMembers)\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Edit Team Members';\n+ Image = Users;\n+ RunObject = Page \"Expense Team Members\";\n+ RunPageLink = \"Team Code\" = field(Code);\n+ ToolTip = 'Add or remove members from the selected expense team.';\n+ }\n+ action(ViewTeamExpenses)\n+ {\n+ ApplicationArea = All;\n+ Caption = 'View Team Expenses';\n+ Image = \"Report\";\n+ ToolTip = 'View all expenses submitted by members of this team.';\n+\n+ trigger OnAction()\n+ var\n+ ExpenseTeamMember: Record \"Expense Team Member\";\n+ begin\n+ ExpenseTeamMember.SetRange(\"Team Code\", Rec.Code);\n+ if ExpenseTeamMember.IsEmpty() then\n+ Message('No members found for team %1', Rec.Code)\n+ else\n+ Page.Run(Page::\"Expense Team Members\", ExpenseTeamMember);\n+ end;\n+ }\n+ }\n+ area(Navigation)\n+ {\n+ group(Team)\n+ {\n+ Caption = 'Team';\n+ action(TeamMembers)\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Team Members';\n+ Image = Users;\n+ RunObject = Page \"Expense Team Members\";\n+ RunPageLink = \"Team Code\" = field(Code);\n+ ToolTip = 'View and manage the members of the selected expense team.';\n+ }\n+ }\n+ }\n+ }\n+\n+ trigger OnNewRecord(BelowxRec: Boolean)\n+ begin\n+ Rec.Active := true;\n+ Rec.\"Max Approval Amount\" := 5000;\n+ end;\n+\n+ procedure CreateDefaultTeams()\n+ var\n+ ExpenseTeam: Record \"Expense Team\";\n+ DefaultTeams: List of [Text];\n+ DefaultDescriptions: List of [Text];\n+ TeamName: Text;\n+ i: Integer;\n+ begin\n+ DefaultTeams.Add('SALES');\n+ DefaultTeams.Add('MARKETING');\n+ DefaultTeams.Add('IT');\n+ DefaultTeams.Add('HR');\n+ DefaultTeams.Add('FINANCE');\n+\n+ DefaultDescriptions.Add('Sales Team Expenses');\n+ DefaultDescriptions.Add('Marketing Department Expenses');\n+ DefaultDescriptions.Add('Information Technology Expenses');\n+ DefaultDescriptions.Add('Human Resources Expenses');\n+ DefaultDescriptions.Add('Finance Department Expenses');\n+\n+ for i := 1 to DefaultTeams.Count() do begin\n+ TeamName := DefaultTeams.Get(i);\n+ if not ExpenseTeam.Get(TeamName) then begin\n+ ExpenseTeam.Init();\n+ ExpenseTeam.Code := CopyStr(TeamName, 1, MaxStrLen(ExpenseTeam.Code));\n+ ExpenseTeam.Name := ExpenseTeam.Code;\n+ ExpenseTeam.Description := CopyStr(DefaultDescriptions.Get(i), 1, MaxStrLen(ExpenseTeam.Description));\n+ ExpenseTeam.Active := true;\n+ ExpenseTeam.\"Max Approval Amount\" := 10000;\n+ ExpenseTeam.Insert(true);\n+ end;\n+ end;\n+\n+ Message('%1 default expense teams have been created.', DefaultTeams.Count());\n+ end;\n+\n+ procedure ValidateTeamSetup(): Boolean\n+ var\n+ ValidationPassed: Boolean;\n+ ErrorMessage: Text;\n+ begin\n+ ValidationPassed := true;\n+\n+ if Rec.\"Team Leader\" = '' then begin\n+ ErrorMessage := 'Team Leader must be specified.';\n+ ValidationPassed := false;\n+ end;\n+\n+ if Rec.\"Default Approver\" = '' then begin\n+ ErrorMessage := 'Default Approver must be specified.';\n+ ValidationPassed := false;\n+ end;\n+\n+ if Rec.\"Max Approval Amount\" <= 0 then begin\n+ ErrorMessage := 'Max Approval Amount must be greater than zero.';\n+ ValidationPassed := false;\n+ end;\n+\n+ if not ValidationPassed then\n+ Error(ErrorMessage);\n+\n+ exit(ValidationPassed);\n+ end;\n+}\n", "expected_comments": [{"file": "src/ExpenseTeams.Page.al", "line_start": 10, "line_end": 10, "body": "Missing AboutTitle and AboutText properties. Other pages in this codebase include these teaching tip properties for user onboarding, but ExpenseTeams.Page.al only has AdditionalSearchTerms. — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/ExpenseTeams.Page.al", "line_start": 89, "line_end": 89, "body": "Hardcoded string in Message() call: 'No members found for team %1' should use a Label variable with Msg suffix (CodeCop AA0217). — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/ExpenseTeams.Page.al", "line_start": 152, "line_end": 152, "body": "Hardcoded string in Message() call: '%1 default expense teams have been created.' should use a Label variable with Msg suffix (CodeCop AA0217). — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/ExpenseTeams.Page.al", "line_start": 163, "line_end": 163, "body": "Hardcoded string 'Team Leader must be specified.' in error path should use a Label variable with Err suffix (CodeCop AA0217). — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/ExpenseTeams.Page.al", "line_start": 168, "line_end": 168, "body": "Hardcoded string 'Default Approver must be specified.' in error path should use a Label variable with Err suffix (CodeCop AA0217). — See agent comment for details.", "severity": "medium", "domain": "style"}, {"file": "src/ExpenseTeams.Page.al", "line_start": 173, "line_end": 173, "body": "Hardcoded string 'Max Approval Amount must be greater than zero.' in error path should use a Label variable with Err suffix (CodeCop AA0217). — See agent comment for details.", "severity": "medium", "domain": "style"}], "match_line_tolerance": 2, "domain": "style", "category": "code-review", "description": "True positive style findings: other_style (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__upgrade-001", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "upgrade"}, "patch": "diff --git a/src/CustomerCardCreditLimitExt.PageExt.al b/src/CustomerCardCreditLimitExt.PageExt.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerCardCreditLimitExt.PageExt.al\n@@ -0,0 +1,14 @@\n+pageextension 50100 \"Customer Card Credit Limit Ext\" extends \"Customer Card\"\n+{\n+ layout\n+ {\n+ addafter(\"Name\")\n+ {\n+ field(\"Credit Limit (LCY)\"; Rec.\"Credit Limit (LCY)\")\n+ {\n+ ApplicationArea = All;\n+ ToolTip = 'Specifies the maximum credit amount that is allowed for the customer.';\n+ }\n+ }\n+ }\n+}\ndiff --git a/src/InlineUpgradeSteps.Codeunit.al b/src/InlineUpgradeSteps.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/InlineUpgradeSteps.Codeunit.al\n@@ -0,0 +1,28 @@\n+codeunit 50361 \"Inline Upgrade Steps\"\n+{\n+ Subtype = Upgrade;\n+\n+ trigger OnUpgradePerCompany()\n+ var\n+ Customer: Record Customer;\n+ UpgradeTag: Codeunit \"Upgrade Tag\";\n+ begin\n+ if UpgradeTag.HasUpgradeTag(this.CustomerFlagsUpgradeTag()) then\n+ exit;\n+\n+ Customer.ModifyAll(\"Privacy Blocked\", false);\n+\n+ UpgradeTag.SetUpgradeTag(this.CustomerFlagsUpgradeTag());\n+ end;\n+\n+ local procedure CustomerFlagsUpgradeTag(): Code[250]\n+ begin\n+ exit('BCBench-InlineUpgradeSteps-20260611');\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerCompanyUpgradeTags', '', false, false)]\n+ local procedure RegisterPerCompanyTags(var PerCompanyUpgradeTags: List of [Code[250]])\n+ begin\n+ PerCompanyUpgradeTags.Add(this.CustomerFlagsUpgradeTag());\n+ end;\n+}\n", "expected_comments": [{"file": "src/InlineUpgradeSteps.Codeunit.al", "line_start": 5, "line_end": 5, "severity": "medium", "domain": "upgrade", "body": "OnUpgradePerCompany trigger contains the upgrade implementation inline. Trigger bodies should delegate to a local procedure so upgrade orchestration and implementation remain separable and testable."}], "match_line_tolerance": 2, "domain": "upgrade", "category": "code-review", "description": "False positive upgrade findings: breaking_change_fp (1 false positives). Agent flagged these but reviewers rejected them.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__upgrade-002", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "upgrade"}, "patch": "diff --git a/src/EnumConversionHelper.Codeunit.al b/src/EnumConversionHelper.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/EnumConversionHelper.Codeunit.al\n@@ -0,0 +1,9 @@\n+codeunit 50101 EnumConversionHelper\n+{\n+ Access = Internal;\n+\n+ procedure GetDefaultPaymentMethodType(): Enum \"Payment Method Type\"\n+ begin\n+ exit(\"Payment Method Type\"::Cash);\n+ end;\n+}\ndiff --git a/src/PaymentMethodType.Enum.al b/src/PaymentMethodType.Enum.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PaymentMethodType.Enum.al\n@@ -0,0 +1,29 @@\n+enum 50100 \"Payment Method Type\"\n+{\n+ Extensible = true;\n+\n+ value(0; Cash)\n+ {\n+ Caption = 'Cash';\n+ }\n+ value(1; Check)\n+ {\n+ Caption = 'Check';\n+ }\n+ value(2; \"Credit Card\")\n+ {\n+ Caption = 'Credit Card';\n+ }\n+ value(3; \"Bank Transfer\")\n+ {\n+ Caption = 'Bank Transfer';\n+ }\n+ value(4; Electronic)\n+ {\n+ Caption = 'Electronic';\n+ }\n+ value(99; Other)\n+ {\n+ Caption = 'Other';\n+ }\n+}\ndiff --git a/src/NoTagCustomerUpgrade.Codeunit.al b/src/NoTagCustomerUpgrade.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/NoTagCustomerUpgrade.Codeunit.al\n@@ -0,0 +1,24 @@\n+codeunit 50362 \"No Tag Customer Upgrade\"\n+{\n+ Subtype = Upgrade;\n+\n+ trigger OnUpgradePerCompany()\n+ begin\n+ this.UpgradeCustomerFlags();\n+ end;\n+\n+ local procedure UpgradeCustomerFlags()\n+ var\n+ Customer: Record Customer;\n+ begin\n+ if not this.ShouldUpgradeCustomerFlags() then\n+ exit;\n+\n+ Customer.ModifyAll(\"Combine Shipments\", true);\n+ end;\n+\n+ local procedure ShouldUpgradeCustomerFlags(): Boolean\n+ begin\n+ exit(true);\n+ end;\n+}\n", "expected_comments": [{"file": "src/NoTagCustomerUpgrade.Codeunit.al", "line_start": 17, "line_end": 17, "severity": "high", "domain": "upgrade", "body": "Upgrade procedure UpgradeCustomerFlags runs without an UpgradeTag check, so it can execute on every upgrade. Use UpgradeTag.HasUpgradeTag and SetUpgradeTag to make the upgrade idempotent."}], "match_line_tolerance": 2, "domain": "upgrade", "category": "code-review", "description": "False positive upgrade findings: enum_conversion_fp (5 false positives). Agent flagged these but reviewers rejected them.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__upgrade-003", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "upgrade"}, "patch": "diff --git a/src/CustomerListEnhancements.PageExt.al b/src/CustomerListEnhancements.PageExt.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CustomerListEnhancements.PageExt.al\n@@ -0,0 +1,47 @@\n+pageextension 50103 CustomerListEnhancements extends \"Customer List\"\n+{\n+ actions\n+ {\n+ addlast(processing)\n+ {\n+ action(ExportToExcel)\n+ {\n+ ApplicationArea = All;\n+ Caption = 'Export to Excel';\n+ Image = Excel;\n+ ToolTip = 'Exports the customer list to an Excel workbook using the standard Excel buffer.';\n+\n+ trigger OnAction()\n+ var\n+ Customer: Record Customer;\n+ TempExcelBuffer: Record \"Excel Buffer\" temporary;\n+ begin\n+ TempExcelBuffer.DeleteAll();\n+\n+ TempExcelBuffer.NewRow();\n+ TempExcelBuffer.AddColumn(CustomerNoCaptionLbl, false, '', false, false, false, '', TempExcelBuffer.\"Cell Type\"::Text);\n+ TempExcelBuffer.AddColumn(NameCaptionLbl, false, '', false, false, false, '', TempExcelBuffer.\"Cell Type\"::Text);\n+\n+ Customer.Copy(Rec);\n+ Customer.SetLoadFields(\"No.\", Name);\n+ if Customer.FindSet() then\n+ repeat\n+ TempExcelBuffer.NewRow();\n+ TempExcelBuffer.AddColumn(Customer.\"No.\", false, '', false, false, false, '', TempExcelBuffer.\"Cell Type\"::Text);\n+ TempExcelBuffer.AddColumn(Customer.Name, false, '', false, false, false, '', TempExcelBuffer.\"Cell Type\"::Text);\n+ until Customer.Next() = 0;\n+\n+ TempExcelBuffer.CreateNewBook(SheetNameLbl);\n+ TempExcelBuffer.WriteSheet(SheetNameLbl, CompanyName(), UserId());\n+ TempExcelBuffer.CloseBook();\n+ TempExcelBuffer.OpenExcel();\n+ end;\n+ }\n+ }\n+ }\n+\n+ var\n+ CustomerNoCaptionLbl: Label 'Customer No.';\n+ NameCaptionLbl: Label 'Name';\n+ SheetNameLbl: Label 'Customers';\n+}\ndiff --git a/src/ModernAPIHelper.Codeunit.al b/src/ModernAPIHelper.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ModernAPIHelper.Codeunit.al\n@@ -0,0 +1,18 @@\n+codeunit 50102 ModernAPIHelper\n+{\n+ Access = Internal;\n+\n+ [Obsolete('Use CustomerExistsV2() instead', '25.0')]\n+ procedure CustomerExists(CustomerNo: Code[20]): Boolean\n+ begin\n+ exit(CustomerExistsV2(CustomerNo));\n+ end;\n+\n+ procedure CustomerExistsV2(CustomerNo: Code[20]): Boolean\n+ var\n+ Customer: Record Customer;\n+ begin\n+ Customer.SetLoadFields(\"No.\");\n+ exit(Customer.Get(CustomerNo));\n+ end;\n+}\ndiff --git a/src/MissingSetTagUpgrade.Codeunit.al b/src/MissingSetTagUpgrade.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/MissingSetTagUpgrade.Codeunit.al\n@@ -0,0 +1,31 @@\n+codeunit 50363 \"Missing Set Tag Upgrade\"\n+{\n+ Subtype = Upgrade;\n+\n+ trigger OnUpgradePerCompany()\n+ begin\n+ this.UpgradeCustomerShipmentSetup();\n+ end;\n+\n+ local procedure UpgradeCustomerShipmentSetup()\n+ var\n+ Customer: Record Customer;\n+ UpgradeTag: Codeunit \"Upgrade Tag\";\n+ begin\n+ if UpgradeTag.HasUpgradeTag(this.CustomerShipmentUpgradeTag()) then\n+ exit;\n+\n+ Customer.ModifyAll(\"Combine Shipments\", true);\n+ end;\n+\n+ local procedure CustomerShipmentUpgradeTag(): Code[250]\n+ begin\n+ exit('BCBench-MissingSetTag-20260611');\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerCompanyUpgradeTags', '', false, false)]\n+ local procedure RegisterPerCompanyTags(var PerCompanyUpgradeTags: List of [Code[250]])\n+ begin\n+ PerCompanyUpgradeTags.Add(this.CustomerShipmentUpgradeTag());\n+ end;\n+}\n", "expected_comments": [{"file": "src/MissingSetTagUpgrade.Codeunit.al", "line_start": 18, "line_end": 18, "severity": "high", "domain": "upgrade", "body": "Upgrade procedure UpgradeCustomerShipmentSetup checks an UpgradeTag but never sets it after completing. Call UpgradeTag.SetUpgradeTag at the end so the upgrade does not re-run."}], "match_line_tolerance": 2, "domain": "upgrade", "category": "code-review", "description": "False positive upgrade findings: obsolete_usage_fp (2 false positives). Agent flagged these but reviewers rejected them.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__upgrade-004", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "upgrade"}, "patch": "diff --git a/src/GenericUpgradeHandler.Codeunit.al b/src/GenericUpgradeHandler.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/GenericUpgradeHandler.Codeunit.al\n@@ -0,0 +1,36 @@\n+codeunit 50104 GenericUpgradeHandler\n+{\n+ Subtype = Upgrade;\n+\n+ trigger OnUpgradePerCompany()\n+ begin\n+ UpgradeCompanyDisplayName();\n+ end;\n+\n+ local procedure UpgradeCompanyDisplayName()\n+ var\n+ CompanyInfo: Record \"Company Information\";\n+ UpgradeTag: Codeunit \"Upgrade Tag\";\n+ begin\n+ if UpgradeTag.HasUpgradeTag(CompanyDisplayNameTag()) then\n+ exit;\n+\n+ if CompanyInfo.Get() then begin\n+ CompanyInfo.\"Ship-to Name\" := CompanyInfo.Name;\n+ CompanyInfo.Modify(false);\n+ end;\n+\n+ UpgradeTag.SetUpgradeTag(CompanyDisplayNameTag());\n+ end;\n+\n+ local procedure CompanyDisplayNameTag(): Code[250]\n+ begin\n+ exit('MS-50104-CompanyDisplayName-20240101');\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerCompanyUpgradeTags', '', false, false)]\n+ local procedure RegisterPerCompanyUpgradeTags(var PerCompanyUpgradeTags: List of [Code[250]])\n+ begin\n+ PerCompanyUpgradeTags.Add(CompanyDisplayNameTag());\n+ end;\n+}\ndiff --git a/src/MigrationStatusTracker.Table.al b/src/MigrationStatusTracker.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/MigrationStatusTracker.Table.al\n@@ -0,0 +1,65 @@\n+table 50105 \"Migration Status Tracker\"\n+{\n+ Caption = 'Migration Status Tracker';\n+ DataClassification = SystemMetadata;\n+ TableType = Temporary;\n+\n+ fields\n+ {\n+ field(1; \"Migration ID\"; Guid)\n+ {\n+ DataClassification = SystemMetadata;\n+ Caption = 'Migration ID';\n+ }\n+ field(2; \"Table Name\"; Text[100])\n+ {\n+ DataClassification = SystemMetadata;\n+ Caption = 'Table Name';\n+ }\n+ field(3; \"Records Processed\"; Integer)\n+ {\n+ DataClassification = SystemMetadata;\n+ Caption = 'Records Processed';\n+ }\n+ field(4; \"Status\"; Option)\n+ {\n+ DataClassification = SystemMetadata;\n+ OptionMembers = Pending,InProgress,Completed,Failed;\n+ OptionCaption = 'Pending,In Progress,Completed,Failed';\n+ Caption = 'Status';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(PK; \"Migration ID\", \"Table Name\")\n+ {\n+ Clustered = true;\n+ }\n+ }\n+\n+ procedure InitializeMigration(TableName: Text[100]): Guid\n+ var\n+ MigrationId: Guid;\n+ begin\n+ MigrationId := CreateGuid();\n+\n+ Rec.Init();\n+ Rec.\"Migration ID\" := MigrationId;\n+ Rec.\"Table Name\" := TableName;\n+ Rec.Status := Rec.Status::Pending;\n+ Rec.\"Records Processed\" := 0;\n+ Rec.Insert(false);\n+\n+ exit(MigrationId);\n+ end;\n+\n+ procedure UpdateProgress(MigrationId: Guid; TableName: Text[100]; RecordsProcessed: Integer)\n+ begin\n+ if Rec.Get(MigrationId, TableName) then begin\n+ Rec.\"Records Processed\" := RecordsProcessed;\n+ Rec.Status := Rec.Status::InProgress;\n+ Rec.Modify(false);\n+ end;\n+ end;\n+}\ndiff --git a/src/CommitInUpgrade.Codeunit.al b/src/CommitInUpgrade.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CommitInUpgrade.Codeunit.al\n@@ -0,0 +1,34 @@\n+codeunit 50364 \"Commit In Upgrade\"\n+{\n+ Subtype = Upgrade;\n+\n+ trigger OnUpgradePerCompany()\n+ begin\n+ this.UpgradeCustomerPrices();\n+ end;\n+\n+ local procedure UpgradeCustomerPrices()\n+ var\n+ Customer: Record Customer;\n+ UpgradeTag: Codeunit \"Upgrade Tag\";\n+ begin\n+ if UpgradeTag.HasUpgradeTag(this.CustomerPricesUpgradeTag()) then\n+ exit;\n+\n+ Customer.ModifyAll(\"Prices Including VAT\", false);\n+ Commit();\n+\n+ UpgradeTag.SetUpgradeTag(this.CustomerPricesUpgradeTag());\n+ end;\n+\n+ local procedure CustomerPricesUpgradeTag(): Code[250]\n+ begin\n+ exit('BCBench-CommitInUpgrade-20260611');\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerCompanyUpgradeTags', '', false, false)]\n+ local procedure RegisterPerCompanyTags(var PerCompanyUpgradeTags: List of [Code[250]])\n+ begin\n+ PerCompanyUpgradeTags.Add(this.CustomerPricesUpgradeTag());\n+ end;\n+}\n", "expected_comments": [{"file": "src/CommitInUpgrade.Codeunit.al", "line_start": 19, "line_end": 19, "severity": "high", "domain": "upgrade", "body": "Commit() is called inside upgrade code. Remove explicit commits because the upgrade framework controls transaction boundaries and rollback behavior."}], "match_line_tolerance": 2, "domain": "upgrade", "category": "code-review", "description": "False positive upgrade findings: other_upgrade (158 false positives). Agent flagged these but reviewers rejected them.", "expect_findings": false, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__upgrade-005", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "upgrade"}, "patch": "diff --git a/src/CurrencySymbolPosition.Enum.al b/src/CurrencySymbolPosition.Enum.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/CurrencySymbolPosition.Enum.al\n@@ -0,0 +1,29 @@\n+enum 764 \"Currency Symbol Position\"\n+{\n+ Extensible = true;\n+\n+ value(0; \"Default\")\n+ {\n+ Caption = 'Default';\n+ }\n+\n+ value(1; \"Before Amount\")\n+ {\n+ Caption = 'Before Amount';\n+ }\n+\n+ value(2; \"After Amount\")\n+ {\n+ Caption = 'After Amount';\n+ }\n+\n+ value(3; \"Before Amount with Space\")\n+ {\n+ Caption = 'Before Amount with Space';\n+ }\n+\n+ value(4; \"After Amount with Space\")\n+ {\n+ Caption = 'After Amount with Space';\n+ }\n+}\ndiff --git a/src/ManufacturingSetup.Table.al b/src/ManufacturingSetup.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ManufacturingSetup.Table.al\n@@ -0,0 +1,42 @@\n+table 99000765 \"Manufacturing Setup\"\n+{\n+ Caption = 'Manufacturing Setup';\n+ DataPerCompany = true;\n+\n+ fields\n+ {\n+ field(1; \"Primary Key\"; Code[10])\n+ {\n+ Caption = 'Primary Key';\n+ NotBlank = true;\n+ DataClassification = SystemMetadata;\n+ }\n+\n+ field(20; \"Default Damping Period\"; DateFormula)\n+ {\n+ Caption = 'Default Damping Period';\n+ DataClassification = SystemMetadata;\n+ }\n+\n+ field(325; \"Copy Loc. to Cap. Val. Entries\"; Boolean)\n+ {\n+ Caption = 'Copy Location Code to Capacity Value Entries';\n+ DataClassification = SystemMetadata;\n+ InitValue = true;\n+ }\n+\n+ field(326; \"Enable Advanced Costing\"; Boolean)\n+ {\n+ Caption = 'Enable Advanced Costing';\n+ DataClassification = SystemMetadata;\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(Key1; \"Primary Key\")\n+ {\n+ Clustered = true;\n+ }\n+ }\n+}\ndiff --git a/src/O365Contact.Table.al b/src/O365Contact.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/O365Contact.Table.al\n@@ -0,0 +1,54 @@\n+table 5367 \"O365 Contact\"\n+{\n+ Caption = 'O365 Contact';\n+ ReplicateData = false;\n+\n+ fields\n+ {\n+ field(1; \"Contact ID\"; Text[250])\n+ {\n+ Caption = 'Contact ID';\n+ Editable = false;\n+ DataClassification = CustomerContent;\n+ }\n+\n+ field(2; \"Contact No.\"; Code[20])\n+ {\n+ Caption = 'Contact No.';\n+ TableRelation = Contact;\n+ DataClassification = CustomerContent;\n+ }\n+\n+ field(3; Name; Text[100])\n+ {\n+ Caption = 'Name';\n+ DataClassification = CustomerContent;\n+ }\n+\n+ field(4; \"E-Mail\"; Text[80])\n+ {\n+ Caption = 'E-Mail';\n+ ExtendedDatatype = EMail;\n+ DataClassification = CustomerContent;\n+ }\n+\n+ field(104; \"Outlook Id\"; Text[250])\n+ {\n+ Caption = 'Outlook ID';\n+ Editable = false;\n+ DataClassification = CustomerContent;\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(Key1; \"Outlook Id\")\n+ {\n+ Clustered = true;\n+ }\n+\n+ key(Key2; \"Contact No.\")\n+ {\n+ }\n+ }\n+}\ndiff --git a/src/PostedExpenseReportLine.Table.al b/src/PostedExpenseReportLine.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PostedExpenseReportLine.Table.al\n@@ -0,0 +1,168 @@\n+table 6913 \"Posted Expense Report Line\"\n+{\n+ Caption = 'Posted Expense Report Line';\n+ DataClassification = CustomerContent;\n+ DrillDownPageID = \"Posted Expense Report Lines\";\n+ LookupPageID = \"Posted Expense Report Lines\";\n+\n+ fields\n+ {\n+ field(1; \"Document No.\"; Code[20])\n+ {\n+ Caption = 'Document No.';\n+ TableRelation = \"Posted Expense Report Header\";\n+ }\n+\n+ field(2; \"Line No.\"; Integer)\n+ {\n+ Caption = 'Line No.';\n+ }\n+\n+ field(3; \"Employee Code\"; Code[20])\n+ {\n+ Caption = 'Employee Code';\n+ TableRelation = Employee;\n+ }\n+\n+ field(4; \"Expense No.\"; Code[20])\n+ {\n+ Caption = 'Expense No.';\n+ TableRelation = \"Expense Category\";\n+ }\n+\n+ field(5; Description; Text[100])\n+ {\n+ Caption = 'Description';\n+ }\n+\n+ field(6; \"Expense Date\"; Date)\n+ {\n+ Caption = 'Expense Date';\n+ }\n+\n+ field(7; Amount; Decimal)\n+ {\n+ Caption = 'Amount';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(8; \"Amount (LCY)\"; Decimal)\n+ {\n+ Caption = 'Amount (LCY)';\n+ DecimalPlaces = 2 : 5;\n+ Editable = false;\n+ }\n+\n+ field(9; \"Currency Code\"; Code[10])\n+ {\n+ Caption = 'Currency Code';\n+ TableRelation = Currency;\n+ }\n+\n+ field(10; \"Currency Factor\"; Decimal)\n+ {\n+ Caption = 'Currency Factor';\n+ DecimalPlaces = 0 : 15;\n+ MinValue = 0;\n+ }\n+\n+ field(11; \"VAT %\"; Decimal)\n+ {\n+ Caption = 'VAT %';\n+ DecimalPlaces = 0 : 5;\n+ MinValue = 0;\n+ MaxValue = 100;\n+ }\n+\n+ field(12; \"VAT Amount\"; Decimal)\n+ {\n+ Caption = 'VAT Amount';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(13; \"VAT Amount (LCY)\"; Decimal)\n+ {\n+ Caption = 'VAT Amount (LCY)';\n+ DecimalPlaces = 2 : 5;\n+ Editable = false;\n+ }\n+\n+ field(14; \"Expense Category Code\"; Code[20])\n+ {\n+ Caption = 'Expense Category Code';\n+ TableRelation = \"Expense Category\";\n+ }\n+\n+ field(15; \"Gen. Prod. Posting Group\"; Code[20])\n+ {\n+ Caption = 'Gen. Prod. Posting Group';\n+ TableRelation = \"Gen. Product Posting Group\";\n+ }\n+\n+ field(16; \"VAT Prod. Posting Group\"; Code[20])\n+ {\n+ Caption = 'VAT Prod. Posting Group';\n+ TableRelation = \"VAT Product Posting Group\";\n+ }\n+\n+ field(17; \"Posting Date\"; Date)\n+ {\n+ Caption = 'Posting Date';\n+ }\n+\n+ field(18; \"Document Date\"; Date)\n+ {\n+ Caption = 'Document Date';\n+ }\n+\n+ field(19; \"Reimbursable\"; Boolean)\n+ {\n+ Caption = 'Reimbursable';\n+ }\n+\n+ field(20; \"Receipt Attached\"; Boolean)\n+ {\n+ Caption = 'Receipt Attached';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(Key1; \"Document No.\", \"Line No.\")\n+ {\n+ Clustered = true;\n+ }\n+ key(Key2; \"Employee Code\", \"Posting Date\")\n+ {\n+ }\n+ key(Key3; \"Expense Category Code\")\n+ {\n+ }\n+ }\n+\n+ trigger OnDelete()\n+ var\n+ PostedExpenseAttachment: Record \"Posted Expense Attachment\";\n+ begin\n+ PostedExpenseAttachment.SetRange(\"Document No.\", \"Document No.\");\n+ PostedExpenseAttachment.SetRange(\"Line No.\", \"Line No.\");\n+ PostedExpenseAttachment.DeleteAll();\n+ end;\n+\n+ procedure ShowReceipts()\n+ var\n+ PostedExpenseAttachment: Record \"Posted Expense Attachment\";\n+ ExpenseAttachmentList: Page \"Posted Expense Attachments\";\n+ begin\n+ PostedExpenseAttachment.SetRange(\"Document No.\", \"Document No.\");\n+ PostedExpenseAttachment.SetRange(\"Line No.\", \"Line No.\");\n+ ExpenseAttachmentList.SetTableView(PostedExpenseAttachment);\n+ ExpenseAttachmentList.RunModal();\n+ end;\n+\n+ procedure CalcVATAmount()\n+ begin\n+ \"VAT Amount\" := Round(Amount * \"VAT %\" / 100, 0.01);\n+ \"VAT Amount (LCY)\" := Round(\"Amount (LCY)\" * \"VAT %\" / 100, 0.01);\n+ end;\n+}\ndiff --git a/src/TaxTransactionValue.Table.al b/src/TaxTransactionValue.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/TaxTransactionValue.Table.al\n@@ -0,0 +1,196 @@\n+table 18221 \"Tax Transaction Value\"\n+{\n+ Caption = 'Tax Transaction Value';\n+ DataClassification = EndUserIdentifiableInformation;\n+\n+ fields\n+ {\n+ field(1; \"Tax Record ID\"; RecordId)\n+ {\n+ Caption = 'Tax Record ID';\n+ DataClassification = SystemMetadata;\n+ }\n+\n+ field(2; \"Value Type\"; Enum \"Tax Value Type\")\n+ {\n+ Caption = 'Value Type';\n+ }\n+\n+ field(3; \"Value ID\"; Integer)\n+ {\n+ Caption = 'Value ID';\n+ }\n+\n+ field(4; \"Column ID\"; Integer)\n+ {\n+ Caption = 'Column ID';\n+ }\n+\n+ field(5; \"Line No.\"; Integer)\n+ {\n+ Caption = 'Line No.';\n+ }\n+\n+ field(6; \"Tax Type\"; Code[20])\n+ {\n+ Caption = 'Tax Type';\n+ TableRelation = \"Tax Type\";\n+ }\n+\n+ field(7; \"Tax Rate ID\"; Guid)\n+ {\n+ Caption = 'Tax Rate ID';\n+ }\n+\n+ field(8; Amount; Decimal)\n+ {\n+ Caption = 'Amount';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(9; \"Amount (LCY)\"; Decimal)\n+ {\n+ Caption = 'Amount (LCY)';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(10; \"Currency Code\"; Code[10])\n+ {\n+ Caption = 'Currency Code';\n+ TableRelation = Currency;\n+ }\n+\n+ field(11; \"Currency Factor\"; Decimal)\n+ {\n+ Caption = 'Currency Factor';\n+ DecimalPlaces = 0 : 15;\n+ MinValue = 0;\n+ }\n+\n+ field(12; Percent; Decimal)\n+ {\n+ Caption = 'Percent';\n+ DecimalPlaces = 0 : 5;\n+ }\n+\n+ field(13; \"Tax Component Code\"; Code[30])\n+ {\n+ Caption = 'Tax Component Code';\n+ TableRelation = \"Tax Component\";\n+ }\n+\n+ field(14; \"Component Calc. Type\"; Enum \"Component Calc Type\")\n+ {\n+ Caption = 'Component Calc. Type';\n+ }\n+\n+ field(15; \"Tax Attribute Value ID\"; Integer)\n+ {\n+ Caption = 'Tax Attribute Value ID';\n+ }\n+\n+ field(16; \"Date Filter From\"; Date)\n+ {\n+ Caption = 'Date Filter From';\n+ FieldClass = FlowFilter;\n+ }\n+\n+ field(17; \"Date Filter To\"; Date)\n+ {\n+ Caption = 'Date Filter To';\n+ FieldClass = FlowFilter;\n+ }\n+\n+ field(18; ID; BigInteger)\n+ {\n+ Caption = 'ID';\n+ AutoIncrement = true;\n+ }\n+\n+ field(19; \"Transaction Type\"; Enum \"Transaction Type\")\n+ {\n+ Caption = 'Transaction Type';\n+ }\n+\n+ field(20; \"Posting Date\"; Date)\n+ {\n+ Caption = 'Posting Date';\n+ }\n+\n+ field(21; \"Document Type\"; Enum \"Gen. Journal Document Type\")\n+ {\n+ Caption = 'Document Type';\n+ }\n+\n+ field(22; \"Document No.\"; Code[20])\n+ {\n+ Caption = 'Document No.';\n+ }\n+\n+ field(23; \"Journal Line No.\"; Integer)\n+ {\n+ Caption = 'Journal Line No.';\n+ }\n+\n+ field(24; \"External Document No.\"; Code[35])\n+ {\n+ Caption = 'External Document No.';\n+ }\n+\n+ field(25; \"Gen. Bus. Posting Group\"; Code[20])\n+ {\n+ Caption = 'Gen. Bus. Posting Group';\n+ TableRelation = \"Gen. Business Posting Group\";\n+ }\n+\n+ field(26; \"Gen. Prod. Posting Group\"; Code[20])\n+ {\n+ Caption = 'Gen. Prod. Posting Group';\n+ TableRelation = \"Gen. Product Posting Group\";\n+ }\n+\n+ field(27; \"Dimension Set ID\"; Integer)\n+ {\n+ Caption = 'Dimension Set ID';\n+ TableRelation = \"Dimension Set Entry\";\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(Key1; ID)\n+ {\n+ Clustered = true;\n+ }\n+\n+ key(Key2; \"Tax Record ID\", \"Value Type\", \"Value ID\", \"Column ID\", \"Line No.\")\n+ {\n+ }\n+\n+ key(Key3; \"Tax Type\", \"Tax Component Code\")\n+ {\n+ }\n+ }\n+\n+ trigger OnInsert()\n+ begin\n+ if \"Posting Date\" = 0D then\n+ \"Posting Date\" := WorkDate();\n+ end;\n+\n+ procedure CalculateTax(var TaxCalculation: Codeunit \"Tax Calculation\")\n+ begin\n+ TaxCalculation.SetTaxTransactionValue(Rec);\n+ TaxCalculation.Calculate();\n+ end;\n+\n+ procedure GetTaxAmount(): Decimal\n+ begin\n+ exit(Amount);\n+ end;\n+\n+ procedure GetTaxPercent(): Decimal\n+ begin\n+ exit(Percent);\n+ end;\n+}\n", "expected_comments": [{"file": "src/CurrencySymbolPosition.Enum.al", "line_start": 5, "line_end": 5, "body": "Enum value re-numbering - Before Amount changed from 0 to 1 — See agent comment for details.", "severity": "high", "domain": "upgrade"}, {"file": "src/ManufacturingSetup.Table.al", "line_start": 25, "line_end": 25, "body": "InitValue = true on new Boolean field without upgrade code — See agent comment for details.", "severity": "high", "domain": "upgrade"}, {"file": "src/O365Contact.Table.al", "line_start": 45, "line_end": 45, "body": "Primary key changed from 'Contact ID' to Outlook Id — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/PostedExpenseReportLine.Table.al", "line_start": 10, "line_end": 10, "body": "Field renumbering - Document No. from field(3) to field(1) — See agent comment for details.", "severity": "critical", "domain": "upgrade"}, {"file": "src/TaxTransactionValue.Table.al", "line_start": 104, "line_end": 104, "body": "Field type change from Integer to BigInteger without upgrade code — See agent comment for details.", "severity": "critical", "domain": "upgrade"}], "match_line_tolerance": 2, "domain": "upgrade", "category": "code-review", "description": "True positive upgrade findings: breaking_change (trimmed to 5 representative findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__upgrade-006", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "upgrade"}, "patch": "diff --git a/src/LogiqUpgrade.Codeunit.al b/src/LogiqUpgrade.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/LogiqUpgrade.Codeunit.al\n@@ -0,0 +1,79 @@\n+codeunit 6195 \"Logiq Upgrade\"\n+{\n+ Subtype = Upgrade;\n+\n+ trigger OnUpgradePerDatabase()\n+ var\n+ UpgradeTag: Codeunit \"Upgrade Tag\";\n+ LogiqUpgradeTags: Codeunit \"Logiq Upgrade Tags\";\n+ begin\n+ if UpgradeTag.HasUpgradeTag(LogiqUpgradeTags.GetLogiqConnectionUpgradeTag()) then\n+ exit;\n+\n+ SetupLogiqServiceConnection();\n+\n+ UpgradeTag.SetUpgradeTag(LogiqUpgradeTags.GetLogiqConnectionUpgradeTag());\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerCompanyUpgradeTags', '', false, false)]\n+ local procedure RegisterPerCompanyTags(var PerCompanyUpgradeTags: List of [Code[250]])\n+ var\n+ LogiqUpgradeTags: Codeunit \"Logiq Upgrade Tags\";\n+ begin\n+ PerCompanyUpgradeTags.Add(LogiqUpgradeTags.GetLogiqDocumentMappingUpgradeTag());\n+ PerCompanyUpgradeTags.Add(LogiqUpgradeTags.GetLogiqWorkflowUpgradeTag());\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerDatabaseUpgradeTags', '', false, false)]\n+ local procedure RegisterPerDatabaseTags(var PerDatabaseUpgradeTags: List of [Code[250]])\n+ var\n+ LogiqUpgradeTags: Codeunit \"Logiq Upgrade Tags\";\n+ begin\n+ PerDatabaseUpgradeTags.Add(LogiqUpgradeTags.GetLogiqConnectionUpgradeTag());\n+ end;\n+\n+ local procedure SetupLogiqServiceConnection()\n+ var\n+ EDocServiceConnection: Record \"E-Document Service\";\n+ begin\n+ if not EDocServiceConnection.Get('LOGIQ') then begin\n+ EDocServiceConnection.Init();\n+ EDocServiceConnection.Code := 'LOGIQ';\n+ EDocServiceConnection.Description := 'Logiq E-Document Service';\n+ EDocServiceConnection.\"Service Integration\" := EDocServiceConnection.\"Service Integration\"::Logiq;\n+ EDocServiceConnection.Enabled := false;\n+ EDocServiceConnection.Insert();\n+ end;\n+ end;\n+\n+ procedure UpgradeLogiqDocumentMappings()\n+ var\n+ LogiqDocumentMapping: Record \"Logiq Document Mapping\";\n+ begin\n+ if LogiqDocumentMapping.Get('PEPPOL') then\n+ exit;\n+\n+ LogiqDocumentMapping.Init();\n+ LogiqDocumentMapping.\"Format Code\" := 'PEPPOL';\n+ LogiqDocumentMapping.Description := 'PEPPOL Format';\n+ LogiqDocumentMapping.\"Mapping Type\" := LogiqDocumentMapping.\"Mapping Type\"::Standard;\n+ LogiqDocumentMapping.Enabled := true;\n+ LogiqDocumentMapping.Insert();\n+ end;\n+\n+ procedure ValidateLogiqConfiguration()\n+ var\n+ LogiqConnection: Record \"Logiq Connection\";\n+ EDocServiceConnection: Record \"E-Document Service\";\n+ begin\n+ if not LogiqConnection.Get() then\n+ Error(LogiqConnectionMissingErr);\n+\n+ if not EDocServiceConnection.Get('LOGIQ') then\n+ Error(LogiqServiceMissingErr);\n+ end;\n+\n+ var\n+ LogiqConnectionMissingErr: Label 'Logiq connection setup is missing. Please configure the Logiq connection.';\n+ LogiqServiceMissingErr: Label 'Logiq service connection is not configured.';\n+}\ndiff --git a/src/TaxTransactionValue.Table.al b/src/TaxTransactionValue.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/TaxTransactionValue.Table.al\n@@ -0,0 +1,179 @@\n+table 18221 \"Tax Transaction Value\"\n+{\n+ Caption = 'Tax Transaction Value';\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"Tax Record ID\"; RecordId)\n+ {\n+ Caption = 'Tax Record ID';\n+ DataClassification = SystemMetadata;\n+ }\n+\n+ field(2; \"Value Type\"; Enum \"Tax Value Type\")\n+ {\n+ Caption = 'Value Type';\n+ }\n+\n+ field(3; \"Value ID\"; Integer)\n+ {\n+ Caption = 'Value ID';\n+ }\n+\n+ field(4; \"Column ID\"; Integer)\n+ {\n+ Caption = 'Column ID';\n+ }\n+\n+ field(5; \"Line No.\"; Integer)\n+ {\n+ Caption = 'Line No.';\n+ }\n+\n+ field(6; \"Tax Type\"; Code[20])\n+ {\n+ Caption = 'Tax Type';\n+ TableRelation = \"Tax Type\";\n+ }\n+\n+ field(7; \"Tax Rate ID\"; Guid)\n+ {\n+ Caption = 'Tax Rate ID';\n+ }\n+\n+ field(8; Amount; Decimal)\n+ {\n+ Caption = 'Amount';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(9; \"Amount (LCY)\"; Decimal)\n+ {\n+ Caption = 'Amount (LCY)';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(10; \"Currency Code\"; Code[10])\n+ {\n+ Caption = 'Currency Code';\n+ TableRelation = Currency;\n+ }\n+\n+ field(11; \"Currency Factor\"; Decimal)\n+ {\n+ Caption = 'Currency Factor';\n+ DecimalPlaces = 0 : 15;\n+ MinValue = 0;\n+ }\n+\n+ field(12; Percent; Decimal)\n+ {\n+ Caption = 'Percent';\n+ DecimalPlaces = 0 : 5;\n+ }\n+\n+ field(13; \"Tax Component Code\"; Code[30])\n+ {\n+ Caption = 'Tax Component Code';\n+ TableRelation = \"Tax Component\";\n+ }\n+\n+ field(14; \"Component Calc. Type\"; Enum \"Component Calc Type\")\n+ {\n+ Caption = 'Component Calc. Type';\n+ }\n+\n+ field(15; \"Tax Attribute Value ID\"; Integer)\n+ {\n+ Caption = 'Tax Attribute Value ID';\n+ }\n+\n+ field(16; \"Date Filter From\"; Date)\n+ {\n+ Caption = 'Date Filter From';\n+ FieldClass = FlowFilter;\n+ }\n+\n+ field(17; \"Date Filter To\"; Date)\n+ {\n+ Caption = 'Date Filter To';\n+ FieldClass = FlowFilter;\n+ }\n+\n+ field(18; ID; BigInteger)\n+ {\n+ Caption = 'ID';\n+ AutoIncrement = true;\n+ }\n+\n+ field(19; \"Transaction Type\"; Enum \"Transaction Type\")\n+ {\n+ Caption = 'Transaction Type';\n+ }\n+\n+ field(20; \"Posting Date\"; Date)\n+ {\n+ Caption = 'Posting Date';\n+ }\n+\n+ field(21; \"Document Type\"; Enum \"Gen. Journal Document Type\")\n+ {\n+ Caption = 'Document Type';\n+ }\n+\n+ field(22; \"Document No.\"; Code[20])\n+ {\n+ Caption = 'Document No.';\n+ }\n+\n+ field(24; \"External Document No.\"; Code[35])\n+ {\n+ Caption = 'External Document No.';\n+ }\n+\n+ field(25; \"Calculation Order\"; Integer)\n+ {\n+ Caption = 'Calculation Order';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(Key1; ID)\n+ {\n+ Clustered = true;\n+ }\n+\n+ key(Key2; \"Tax Record ID\", \"Value Type\", \"Value ID\", \"Column ID\", \"Line No.\")\n+ {\n+ }\n+\n+ key(Key3; \"Tax Type\", \"Tax Component Code\")\n+ {\n+ }\n+\n+ key(Key4; \"Posting Date\", \"Document Type\", \"Document No.\")\n+ {\n+ }\n+ }\n+\n+ trigger OnInsert()\n+ begin\n+ if \"Posting Date\" = 0D then\n+ \"Posting Date\" := WorkDate();\n+ end;\n+\n+ procedure GetTaxAmount(): Decimal\n+ begin\n+ exit(Amount);\n+ end;\n+\n+ procedure CalculateTaxAmount(BaseAmount: Decimal): Decimal\n+ begin\n+ if Percent = 0 then\n+ exit(Amount);\n+\n+ exit(Round(BaseAmount * Percent / 100, 0.01));\n+ end;\n+}\ndiff --git a/src/PageroUpgrade.Codeunit.al b/src/PageroUpgrade.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/PageroUpgrade.Codeunit.al\n@@ -0,0 +1,63 @@\n+codeunit 6171 \"Pagero Upgrade\"\n+{\n+ Subtype = Upgrade;\n+\n+ trigger OnUpgradePerDatabase()\n+ var\n+ UpgradeTag: Codeunit \"Upgrade Tag\";\n+ PageroUpgradeTags: Codeunit \"Pagero Upgrade Tags\";\n+ begin\n+ if UpgradeTag.HasUpgradeTag(PageroUpgradeTags.GetPageroConnectionSetupUpgradeTag()) then\n+ exit;\n+\n+ SetupDefaultPageroConnection();\n+\n+ UpgradeTag.SetUpgradeTag(PageroUpgradeTags.GetPageroConnectionSetupUpgradeTag());\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerCompanyUpgradeTags', '', false, false)]\n+ local procedure RegisterPerCompanyTags(var PerCompanyUpgradeTags: List of [Code[250]])\n+ var\n+ PageroUpgradeTags: Codeunit \"Pagero Upgrade Tags\";\n+ begin\n+ PerCompanyUpgradeTags.Add(PageroUpgradeTags.GetPageroDocumentLayoutUpgradeTag());\n+ PerCompanyUpgradeTags.Add(PageroUpgradeTags.GetPageroServiceConnectionUpgradeTag());\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerDatabaseUpgradeTags', '', false, false)]\n+ local procedure RegisterPerDatabaseTags(var PerDatabaseUpgradeTags: List of [Code[250]])\n+ var\n+ PageroUpgradeTags: Codeunit \"Pagero Upgrade Tags\";\n+ begin\n+ PerDatabaseUpgradeTags.Add(PageroUpgradeTags.GetPageroConnectionSetupUpgradeTag());\n+ end;\n+\n+ local procedure SetupDefaultPageroConnection()\n+ var\n+ EDocServiceConnection: Record \"E-Document Service\";\n+ begin\n+ if not EDocServiceConnection.Get('PAGERO') then begin\n+ EDocServiceConnection.Init();\n+ EDocServiceConnection.Code := 'PAGERO';\n+ EDocServiceConnection.Description := 'Pagero E-Document Service';\n+ EDocServiceConnection.\"Service Integration\" := EDocServiceConnection.\"Service Integration\"::Pagero;\n+ EDocServiceConnection.Enabled := false;\n+ EDocServiceConnection.Insert();\n+ end;\n+ end;\n+\n+ procedure UpgradePageroDocumentLayouts()\n+ var\n+ PageroDocumentLayout: Record \"Pagero Document Layout\";\n+ begin\n+ if PageroDocumentLayout.Get(PageroDocumentLayout.\"Document Type\"::Invoice, 'PEPPOL_BIS3') then\n+ exit;\n+\n+ PageroDocumentLayout.Init();\n+ PageroDocumentLayout.\"Document Type\" := PageroDocumentLayout.\"Document Type\"::Invoice;\n+ PageroDocumentLayout.\"Layout Code\" := 'PEPPOL_BIS3';\n+ PageroDocumentLayout.Description := 'PEPPOL BIS3 Format';\n+ PageroDocumentLayout.Enabled := true;\n+ PageroDocumentLayout.Insert();\n+ end;\n+}\ndiff --git a/src/UpgradeExpenseAgentSetup.Codeunit.al b/src/UpgradeExpenseAgentSetup.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/UpgradeExpenseAgentSetup.Codeunit.al\n@@ -0,0 +1,54 @@\n+codeunit 69135 \"Upgrade Expense Agent Setup\"\n+{\n+ Subtype = Upgrade;\n+\n+ trigger OnUpgradePerDatabase()\n+ var\n+ InstallExpenseAgentSetup: Codeunit \"Install Expense Agent Setup\";\n+ begin\n+ InstallExpenseAgentSetup.RegisterCapability();\n+ end;\n+\n+ trigger OnUpgradePerCompany()\n+ var\n+ ExpenseAgentSetup: Record \"Expense Agent Setup\";\n+ UpgradeTag: Codeunit \"Upgrade Tag\";\n+ ExpenseAgentUpgradeTags: Codeunit \"Expense Agent Upgrade Tags\";\n+ begin\n+ if UpgradeTag.HasUpgradeTag(ExpenseAgentUpgradeTags.GetExpenseAgentSetupUpgradeTag()) then\n+ exit;\n+\n+ if not ExpenseAgentSetup.Get() then begin\n+ ExpenseAgentSetup.Init();\n+ ExpenseAgentSetup.Insert();\n+ end;\n+\n+ ExpenseAgentSetup.\"Enable AI Processing\" := true;\n+ ExpenseAgentSetup.\"Max File Size (MB)\" := 10;\n+ ExpenseAgentSetup.\"Supported File Types\" := 'PDF,JPG,PNG,JPEG';\n+ ExpenseAgentSetup.Modify();\n+\n+ UpgradeTag.SetUpgradeTag(ExpenseAgentUpgradeTags.GetExpenseAgentSetupUpgradeTag());\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerCompanyUpgradeTags', '', false, false)]\n+ local procedure RegisterPerCompanyTags(var PerCompanyUpgradeTags: List of [Code[250]])\n+ var\n+ ExpenseAgentUpgradeTags: Codeunit \"Expense Agent Upgrade Tags\";\n+ begin\n+ PerCompanyUpgradeTags.Add(ExpenseAgentUpgradeTags.GetExpenseAgentSetupUpgradeTag());\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerDatabaseUpgradeTags', '', false, false)]\n+ local procedure RegisterPerDatabaseTags(var PerDatabaseUpgradeTags: List of [Code[250]])\n+ begin\n+ end;\n+\n+ procedure UpgradeExpenseCategories()\n+ var\n+ ExpenseCategory: Record \"Expense Category\";\n+ begin\n+ ExpenseCategory.SetRange(\"G/L Account No.\", '');\n+ ExpenseCategory.ModifyAll(\"G/L Account No.\", '6110');\n+ end;\n+}\n", "expected_comments": [{"file": "src/UpgradeExpenseAgentSetup.Codeunit.al", "line_start": 9, "line_end": 9, "body": "OnUpgradePerDatabase calls RegisterCapability without upgrade tag guard — See agent comment for details.", "severity": "critical", "domain": "upgrade"}, {"file": "src/UpgradeExpenseAgentSetup.Codeunit.al", "line_start": 43, "line_end": 43, "body": "RegisterPerDatabaseTags subscriber body is empty so per-database upgrade tag is never registered — See agent comment for details.", "severity": "high", "domain": "upgrade"}, {"file": "src/UpgradeExpenseAgentSetup.Codeunit.al", "line_start": 12, "line_end": 12, "body": "OnUpgradePerCompany contains direct implementation instead of delegating to a named local procedure — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/LogiqUpgrade.Codeunit.al", "line_start": 5, "line_end": 5, "body": "OnUpgradePerDatabase contains direct implementation instead of delegating to a single local procedure — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/TaxTransactionValue.Table.al", "line_start": 104, "line_end": 104, "body": "Field type change from Integer to BigInteger requires upgrade code — See agent comment for details.", "severity": "critical", "domain": "upgrade"}, {"file": "src/PageroUpgrade.Codeunit.al", "line_start": 5, "line_end": 5, "body": "OnUpgradePerDatabase contains direct implementation instead of delegating to a single local procedure — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/LogiqUpgrade.Codeunit.al", "line_start": 49, "line_end": 49, "body": "Public upgrade procedure UpgradeLogiqDocumentMappings without upgrade tag guard — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/PageroUpgrade.Codeunit.al", "line_start": 49, "line_end": 49, "body": "Public upgrade procedure UpgradePageroDocumentLayouts without upgrade tag guard — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/UpgradeExpenseAgentSetup.Codeunit.al", "line_start": 47, "line_end": 47, "body": "Public upgrade procedure UpgradeExpenseCategories without upgrade tag guard — See agent comment for details.", "severity": "medium", "domain": "upgrade"}], "match_line_tolerance": 2, "domain": "upgrade", "category": "code-review", "description": "True positive upgrade findings: data_upgrade (trimmed to representative upgrade-trigger and tag-guard issues)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__upgrade-007", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "upgrade"}, "patch": "diff --git a/src/ColumnHeaderDateType.Enum.al b/src/ColumnHeaderDateType.Enum.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ColumnHeaderDateType.Enum.al\n@@ -0,0 +1,33 @@\n+/// \n+/// Column Header Date Type Enum (764)\n+/// Defines the date type for column headers in financial reports\n+/// \n+enum 764 \"Column Header Date Type\"\n+{\n+ Extensible = true;\n+\n+ value(0; \"Starting Date\")\n+ {\n+ Caption = 'Starting Date';\n+ }\n+\n+ value(1; \"Ending Date\")\n+ {\n+ Caption = 'Ending Date';\n+ }\n+\n+ value(2; \"Date Range\")\n+ {\n+ Caption = 'Date Range';\n+ }\n+\n+ value(3; \"Period\")\n+ {\n+ Caption = 'Period';\n+ }\n+\n+ value(4; \"Closing Date\")\n+ {\n+ Caption = 'Closing Date';\n+ }\n+}\ndiff --git a/src/ExpenseAgentSetup.Table.al b/src/ExpenseAgentSetup.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ExpenseAgentSetup.Table.al\n@@ -0,0 +1,64 @@\n+table 69130 \"Expense Agent Setup\"\n+{\n+ Caption = 'Expense Agent Setup';\n+ DataPerCompany = true;\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"Primary Key\"; Code[10])\n+ {\n+ Caption = 'Primary Key';\n+ NotBlank = true;\n+ }\n+\n+ field(10; \"Enable AI Processing\"; Boolean)\n+ {\n+ Caption = 'Enable AI Processing';\n+ ToolTip = 'Specifies whether AI-based expense processing is enabled.';\n+ }\n+\n+ field(13; \"Max File Size (MB)\"; Integer)\n+ {\n+ Caption = 'Max File Size (MB)';\n+ ToolTip = 'Specifies the maximum allowed receipt file size in megabytes.';\n+ MinValue = 1;\n+ MaxValue = 100;\n+ }\n+\n+ field(350; \"Open Report Notification Frequency\"; Enum \"Notification Frequency\")\n+ {\n+ Caption = 'Open Report Notification Frequency';\n+ ToolTip = 'Specifies how often users are notified about open expense reports.';\n+ InitValue = Daily;\n+ }\n+\n+ field(351; \"Enable Email Notifications\"; Boolean)\n+ {\n+ Caption = 'Enable Email Notifications';\n+ ToolTip = 'Specifies whether email notifications are sent for expense reports.';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(Key1; \"Primary Key\")\n+ {\n+ Clustered = true;\n+ }\n+ }\n+\n+ procedure GetNotificationFrequencyDays(): Integer\n+ begin\n+ case \"Open Report Notification Frequency\" of\n+ \"Open Report Notification Frequency\"::Daily:\n+ exit(1);\n+ \"Open Report Notification Frequency\"::Weekly:\n+ exit(7);\n+ \"Open Report Notification Frequency\"::Monthly:\n+ exit(30);\n+ else\n+ exit(0);\n+ end;\n+ end;\n+}\ndiff --git a/src/ShowCurrencyGenLedgSetup.TableExt.al b/src/ShowCurrencyGenLedgSetup.TableExt.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ShowCurrencyGenLedgSetup.TableExt.al\n@@ -0,0 +1,44 @@\n+tableextension 50200 \"Show Currency Gen Ledg Setup\" extends \"General Ledger Setup\"\n+{\n+ fields\n+ {\n+ field(50200; \"Show Currency Code\"; Boolean)\n+ {\n+ Caption = 'Show Currency Code';\n+ ToolTip = 'Specifies whether the currency code is shown in general ledger views.';\n+ }\n+\n+ field(50201; \"Currency Symbol Position\"; Enum \"Currency Symbol Position\")\n+ {\n+ Caption = 'Currency Symbol Position';\n+ ToolTip = 'Specifies where the currency symbol is positioned relative to amounts.';\n+ InitValue = \"Before Amount\";\n+ }\n+\n+ field(50202; \"Show Currency Symbol\"; Boolean)\n+ {\n+ Caption = 'Show Currency Symbol';\n+ ToolTip = 'Specifies whether the currency symbol is shown next to amounts.';\n+ }\n+\n+ field(50203; \"Currency Decimal Places\"; Integer)\n+ {\n+ Caption = 'Currency Decimal Places';\n+ ToolTip = 'Specifies the number of decimal places used when displaying currency amounts.';\n+ InitValue = 2;\n+ MinValue = 0;\n+ MaxValue = 5;\n+ }\n+ }\n+\n+ procedure SetDefaultCurrencySymbolPosition()\n+ var\n+ GeneralLedgerSetup: Record \"General Ledger Setup\";\n+ begin\n+ GeneralLedgerSetup.Get();\n+ if GeneralLedgerSetup.\"Currency Symbol Position\" = GeneralLedgerSetup.\"Currency Symbol Position\"::\"Default\" then begin\n+ GeneralLedgerSetup.\"Currency Symbol Position\" := GeneralLedgerSetup.\"Currency Symbol Position\"::\"Before Amount\";\n+ GeneralLedgerSetup.Modify();\n+ end;\n+ end;\n+}\n", "expected_comments": [{"file": "src/ColumnHeaderDateType.Enum.al", "line_start": 5, "line_end": 5, "body": "Enum ID changed from 5002000 to 764 — See agent comment for details.", "severity": "critical", "domain": "upgrade"}, {"file": "src/ExpenseAgentSetup.Table.al", "line_start": 33, "line_end": 33, "body": "InitValue = Daily added without upgrade code — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/ShowCurrencyGenLedgSetup.TableExt.al", "line_start": 15, "line_end": 15, "body": "InitValue with enum re-numbering issue — See agent comment for details.", "severity": "high", "domain": "upgrade"}, {"file": "src/ShowCurrencyGenLedgSetup.TableExt.al", "line_start": 28, "line_end": 28, "body": "InitValue = 2 on Currency Decimal Places without upgrade code for existing record — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/ShowCurrencyGenLedgSetup.TableExt.al", "line_start": 34, "line_end": 34, "body": "SetShowCurrencySymbolPosition not in upgrade codeunit, no tag guard, unprotected Get() — See agent comment for details.", "severity": "high", "domain": "upgrade"}], "match_line_tolerance": 2, "domain": "upgrade", "category": "code-review", "description": "True positive upgrade findings: enum_conversion (trimmed to reliably detected enum/initvalue risks)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__upgrade-008", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "upgrade"}, "patch": "diff --git a/src/OIOUBLInitialize.Codeunit.al b/src/OIOUBLInitialize.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/OIOUBLInitialize.Codeunit.al\n@@ -0,0 +1,78 @@\n+codeunit 13631 \"OIOUBL-Initialize\"\n+{\n+ Subtype = Install;\n+\n+ trigger OnInstallAppPerCompany()\n+ var\n+ AppInfo: ModuleInfo;\n+ begin\n+ NavApp.GetCurrentModuleInfo(AppInfo);\n+\n+ if AppInfo.DataVersion = Version.Create(0, 0, 0, 0) then\n+ SetupOIOUBLDefaults()\n+ else\n+ HandleOIOUBLUpgrade(AppInfo.DataVersion);\n+ end;\n+\n+ trigger OnInstallAppPerDatabase()\n+ begin\n+ SetupOIOUBLReportSelections();\n+ end;\n+\n+ local procedure HandleOIOUBLUpgrade(AppVersion: Version)\n+ begin\n+ if AppVersion < Version.Create(25, 0, 0, 0) then\n+ UpgradeToV25();\n+ end;\n+\n+ local procedure SetupOIOUBLDefaults()\n+ var\n+ CompanyInformation: Record \"Company Information\";\n+ OIOUBLProfile: Record \"OIOUBL-Profile\";\n+ begin\n+ if not OIOUBLProfile.Get() then begin\n+ OIOUBLProfile.Init();\n+ OIOUBLProfile.\"OIOUBL Code\" := 'DEFAULT';\n+ OIOUBLProfile.\"OIOUBL Path\" := 'OIOUBL';\n+ OIOUBLProfile.\"Check Company\" := true;\n+ OIOUBLProfile.\"Check Customer\" := true;\n+ OIOUBLProfile.\"Check Item\" := true;\n+ OIOUBLProfile.Insert();\n+ end;\n+\n+ if CompanyInformation.Get() then\n+ if CompanyInformation.\"Country/Region Code\" = 'DK' then begin\n+ CompanyInformation.\"OIOUBL-Profile Code\" := 'DEFAULT';\n+ CompanyInformation.Modify();\n+ end;\n+ end;\n+\n+ local procedure SetupOIOUBLReportSelections()\n+ var\n+ ReportSelections: Record \"Report Selections\";\n+ OIOUBLManagement: Codeunit \"OIOUBL-Management\";\n+ begin\n+ OIOUBLManagement.InsertOIOUBLReportSelections(ReportSelections.Usage::\"S.Invoice\", Report::\"OIOUBL-Sales Invoice\");\n+ OIOUBLManagement.InsertOIOUBLReportSelections(ReportSelections.Usage::\"S.Cr.Memo\", Report::\"OIOUBL-Sales Cr. Memo\");\n+ OIOUBLManagement.InsertOIOUBLReportSelections(ReportSelections.Usage::\"Reminder\", Report::\"OIOUBL-Reminder\");\n+ OIOUBLManagement.InsertOIOUBLReportSelections(ReportSelections.Usage::\"Fin.Charge\", Report::\"OIOUBL-Fin. Charge Memo\");\n+ end;\n+\n+ local procedure UpgradeToV25()\n+ var\n+ OIOUBLProfile: Record \"OIOUBL-Profile\";\n+ GLSetup: Record \"General Ledger Setup\";\n+ begin\n+ if OIOUBLProfile.Get() then begin\n+ OIOUBLProfile.\"Check Item Reference\" := true;\n+ OIOUBLProfile.\"Validate Line Discount\" := true;\n+ OIOUBLProfile.Modify();\n+ end;\n+\n+ if GLSetup.Get() then\n+ if GLSetup.\"Country/Region Code\" = 'DK' then begin\n+ GLSetup.\"OIOUBL Enabled\" := true;\n+ GLSetup.Modify();\n+ end;\n+ end;\n+}\ndiff --git a/src/WHTPurchTaxCrMemoHdr.Table.al b/src/WHTPurchTaxCrMemoHdr.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/WHTPurchTaxCrMemoHdr.Table.al\n@@ -0,0 +1,195 @@\n+table 28047 \"WHT Purch. Tax Cr. Memo Hdr.\"\n+{\n+ Caption = 'WHT Purch. Tax Cr. Memo Hdr.';\n+ DataClassification = CustomerContent;\n+ ObsoleteState = Removed;\n+ ObsoleteReason = 'Replaced with standard Purchase Credit Memo with WHT extensions';\n+ ObsoleteTag = '26.0';\n+\n+ fields\n+ {\n+ field(1; \"No.\"; Code[20])\n+ {\n+ Caption = 'No.';\n+ }\n+\n+ field(2; \"Buy-from Vendor No.\"; Code[20])\n+ {\n+ Caption = 'Buy-from Vendor No.';\n+ TableRelation = Vendor;\n+ }\n+\n+ field(3; \"Buy-from Vendor Name\"; Text[100])\n+ {\n+ Caption = 'Buy-from Vendor Name';\n+ }\n+\n+ field(4; \"Buy-from Address\"; Text[100])\n+ {\n+ Caption = 'Buy-from Address';\n+ }\n+\n+ field(5; \"Buy-from City\"; Text[30])\n+ {\n+ Caption = 'Buy-from City';\n+ }\n+\n+ field(6; \"Buy-from Contact\"; Text[100])\n+ {\n+ Caption = 'Buy-from Contact';\n+ }\n+\n+ field(7; \"Posting Date\"; Date)\n+ {\n+ Caption = 'Posting Date';\n+ }\n+\n+ field(8; \"Document Date\"; Date)\n+ {\n+ Caption = 'Document Date';\n+ }\n+\n+ field(9; \"Due Date\"; Date)\n+ {\n+ Caption = 'Due Date';\n+ }\n+\n+ field(10; \"Payment Discount %\"; Decimal)\n+ {\n+ Caption = 'Payment Discount %';\n+ DecimalPlaces = 0 : 5;\n+ }\n+\n+ field(11; \"Payment Terms Code\"; Code[10])\n+ {\n+ Caption = 'Payment Terms Code';\n+ TableRelation = \"Payment Terms\";\n+ }\n+\n+ field(12; \"Currency Code\"; Code[10])\n+ {\n+ Caption = 'Currency Code';\n+ TableRelation = Currency;\n+ }\n+\n+ field(13; \"Currency Factor\"; Decimal)\n+ {\n+ Caption = 'Currency Factor';\n+ DecimalPlaces = 0 : 15;\n+ }\n+\n+ field(14; Amount; Decimal)\n+ {\n+ Caption = 'Amount';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(15; \"Amount Including VAT\"; Decimal)\n+ {\n+ Caption = 'Amount Including VAT';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(16; \"WHT Business Posting Group\"; Code[20])\n+ {\n+ Caption = 'WHT Business Posting Group';\n+ TableRelation = \"WHT Business Posting Group\";\n+ }\n+\n+ field(17; \"WHT Product Posting Group\"; Code[20])\n+ {\n+ Caption = 'WHT Product Posting Group';\n+ TableRelation = \"WHT Product Posting Group\";\n+ }\n+\n+ field(18; \"WHT Amount\"; Decimal)\n+ {\n+ Caption = 'WHT Amount';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(19; \"WHT Amount (LCY)\"; Decimal)\n+ {\n+ Caption = 'WHT Amount (LCY)';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(20; \"WHT %\"; Decimal)\n+ {\n+ Caption = 'WHT %';\n+ DecimalPlaces = 0 : 5;\n+ }\n+\n+ field(21; \"WHT Certificate No.\"; Code[20])\n+ {\n+ Caption = 'WHT Certificate No.';\n+ }\n+\n+ field(22; \"Vendor Cr. Memo No.\"; Code[35])\n+ {\n+ Caption = 'Vendor Cr. Memo No.';\n+ }\n+\n+ field(23; \"Gen. Bus. Posting Group\"; Code[20])\n+ {\n+ Caption = 'Gen. Bus. Posting Group';\n+ TableRelation = \"Gen. Business Posting Group\";\n+ }\n+\n+ field(24; \"VAT Bus. Posting Group\"; Code[20])\n+ {\n+ Caption = 'VAT Bus. Posting Group';\n+ TableRelation = \"VAT Business Posting Group\";\n+ }\n+\n+ field(25; \"Reason Code\"; Code[10])\n+ {\n+ Caption = 'Reason Code';\n+ TableRelation = \"Reason Code\";\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(Key1; \"No.\")\n+ {\n+ Clustered = true;\n+ }\n+\n+ key(Key2; \"Buy-from Vendor No.\", \"Posting Date\")\n+ {\n+ }\n+\n+ key(Key3; \"WHT Business Posting Group\", \"WHT Product Posting Group\")\n+ {\n+ }\n+ }\n+\n+ trigger OnDelete()\n+ var\n+ WHTEntry: Record \"WHT Entry\";\n+ WHTCertificate: Record \"WHT Certificate\";\n+ begin\n+ WHTEntry.SetRange(\"Document No.\", \"No.\");\n+ WHTEntry.DeleteAll();\n+\n+ if \"WHT Certificate No.\" <> '' then begin\n+ WHTCertificate.SetRange(\"Certificate No.\", \"WHT Certificate No.\");\n+ WHTCertificate.DeleteAll();\n+ end;\n+ end;\n+\n+ procedure CalcWHTAmount()\n+ begin\n+ if \"WHT %\" <> 0 then begin\n+ \"WHT Amount\" := Round(Amount * \"WHT %\" / 100, 0.01);\n+ if \"Currency Factor\" <> 0 then\n+ \"WHT Amount (LCY)\" := Round(\"WHT Amount\" / \"Currency Factor\", 0.01)\n+ else\n+ \"WHT Amount (LCY)\" := \"WHT Amount\";\n+ end else begin\n+ \"WHT Amount\" := 0;\n+ \"WHT Amount (LCY)\" := 0;\n+ end;\n+ end;\n+}\n", "expected_comments": [{"file": "src/OIOUBLInitialize.Codeunit.al", "line_start": 24, "line_end": 24, "body": "Version check pattern instead of upgrade tags - not idempotent — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/OIOUBLInitialize.Codeunit.al", "line_start": 61, "line_end": 61, "body": "UpgradeToV25 procedure lacks upgrade tag to prevent re-execution — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/WHTPurchTaxCrMemoHdr.Table.al", "line_start": 5, "line_end": 5, "body": "Table marked ObsoleteState = Removed without corresponding upgrade code for data migration — See agent comment for details.", "severity": "high", "domain": "upgrade"}, {"file": "src/WHTPurchTaxCrMemoHdr.Table.al", "line_start": 168, "line_end": 168, "body": "OnDelete trigger on removed table can cascade-delete related WHT records during cleanup or migration — See agent comment for details.", "severity": "medium", "domain": "upgrade"}], "match_line_tolerance": 2, "domain": "upgrade", "category": "code-review", "description": "True positive upgrade findings: obsolete_usage (trimmed to reliably detected findings)", "expect_findings": true, "source": "vsoadmin"} +{"repo": "microsoft/BCApps", "instance_id": "synthetic__upgrade-009", "base_commit": "70fd0246a0a4dbc72cb183ca719106722c03be4d", "created_at": "2026-05-15T00:00:00Z", "environment_setup_version": "27.0", "project_paths": [], "metadata": {"area": "upgrade"}, "patch": "diff --git a/src/ContactSyncFolder.Table.al b/src/ContactSyncFolder.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/ContactSyncFolder.Table.al\n@@ -0,0 +1,234 @@\n+/// \n+/// Contact Sync Folder Table (5368)\n+/// Stores folder information for contact synchronization with external systems\n+/// \n+table 5368 \"Contact Sync Folder\"\n+{\n+ Caption = 'Contact Sync Folder';\n+ DataClassification = CustomerContent;\n+\n+ fields\n+ {\n+ field(1; \"Folder ID\"; Text[250])\n+ {\n+ Caption = 'Folder ID';\n+ NotBlank = true;\n+ }\n+\n+ field(2; Name; Text[100])\n+ {\n+ Caption = 'Name';\n+ }\n+\n+ field(3; \"Last Sync DateTime\"; DateTime)\n+ {\n+ Caption = 'Last Sync DateTime';\n+ Editable = false;\n+ }\n+\n+ field(4; \"Parent Id\"; Text[250])\n+ {\n+ Caption = 'Parent Id';\n+ }\n+\n+ field(5; \"Folder Type\"; Option)\n+ {\n+ Caption = 'Folder Type';\n+ OptionCaption = 'Root,Contacts,Companies,People,Distribution Lists';\n+ OptionMembers = Root,Contacts,Companies,People,\"Distribution Lists\";\n+ }\n+\n+ field(6; \"Sync Enabled\"; Boolean)\n+ {\n+ Caption = 'Sync Enabled';\n+ InitValue = true;\n+ }\n+\n+ field(7; \"Sync Direction\"; Option)\n+ {\n+ Caption = 'Sync Direction';\n+ OptionCaption = 'Bidirectional,To Exchange,From Exchange';\n+ OptionMembers = Bidirectional,\"To Exchange\",\"From Exchange\";\n+ InitValue = Bidirectional;\n+ }\n+\n+ field(8; \"Exchange Service\"; Code[20])\n+ {\n+ Caption = 'Exchange Service';\n+ TableRelation = \"Exchange Service Connection\";\n+ }\n+\n+ field(9; \"Total Items\"; Integer)\n+ {\n+ Caption = 'Total Items';\n+ Editable = false;\n+ }\n+\n+ field(10; \"Synced Items\"; Integer)\n+ {\n+ Caption = 'Synced Items';\n+ Editable = false;\n+ }\n+\n+ field(11; \"Pending Items\"; Integer)\n+ {\n+ Caption = 'Pending Items';\n+ Editable = false;\n+ CalcFormula = Count(\"Contact Sync Entry\" WHERE(\"Folder ID\" = FIELD(\"Folder ID\"), \"Sync Status\" = CONST(Pending)));\n+ FieldClass = FlowField;\n+ }\n+\n+ field(12; \"Error Items\"; Integer)\n+ {\n+ Caption = 'Error Items';\n+ Editable = false;\n+ CalcFormula = Count(\"Contact Sync Entry\" WHERE(\"Folder ID\" = FIELD(\"Folder ID\"), \"Sync Status\" = CONST(Error)));\n+ FieldClass = FlowField;\n+ }\n+\n+ field(13; \"Auto Sync Interval\"; Duration)\n+ {\n+ Caption = 'Auto Sync Interval';\n+ }\n+\n+ field(14; \"Next Sync DateTime\"; DateTime)\n+ {\n+ Caption = 'Next Sync DateTime';\n+ }\n+\n+ field(15; \"Conflict Resolution\"; Option)\n+ {\n+ Caption = 'Conflict Resolution';\n+ OptionCaption = 'Exchange Wins,Business Central Wins,Manual Resolution';\n+ OptionMembers = \"Exchange Wins\",\"Business Central Wins\",\"Manual Resolution\";\n+ InitValue = \"Manual Resolution\";\n+ }\n+\n+ field(16; \"Created By\"; Code[50])\n+ {\n+ Caption = 'Created By';\n+ Editable = false;\n+ TableRelation = User.\"User Name\";\n+ }\n+\n+ field(17; \"Created DateTime\"; DateTime)\n+ {\n+ Caption = 'Created DateTime';\n+ Editable = false;\n+ }\n+\n+ field(18; \"Modified By\"; Code[50])\n+ {\n+ Caption = 'Modified By';\n+ Editable = false;\n+ TableRelation = User.\"User Name\";\n+ }\n+\n+ field(19; \"Modified DateTime\"; DateTime)\n+ {\n+ Caption = 'Modified DateTime';\n+ Editable = false;\n+ }\n+\n+ field(20; Blocked; Boolean)\n+ {\n+ Caption = 'Blocked';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(Key1; \"Folder ID\")\n+ {\n+ Clustered = true;\n+ }\n+\n+ key(Key2; \"Exchange Service\", \"Sync Enabled\")\n+ {\n+ }\n+\n+ key(Key3; \"Parent Id\", \"Folder Type\")\n+ {\n+ }\n+\n+ key(Key4; \"Next Sync DateTime\")\n+ {\n+ }\n+ }\n+\n+ fieldgroups\n+ {\n+ fieldgroup(DropDown; \"Folder ID\", Name, \"Folder Type\", \"Sync Enabled\")\n+ {\n+ }\n+\n+ fieldgroup(Brick; \"Folder ID\", Name, \"Total Items\", \"Last Sync DateTime\")\n+ {\n+ }\n+ }\n+\n+ trigger OnInsert()\n+ begin\n+ \"Created By\" := UserId;\n+ \"Created DateTime\" := CurrentDateTime;\n+ \"Modified By\" := UserId;\n+ \"Modified DateTime\" := CurrentDateTime;\n+ end;\n+\n+ trigger OnModify()\n+ begin\n+ \"Modified By\" := UserId;\n+ \"Modified DateTime\" := CurrentDateTime;\n+ end;\n+\n+ trigger OnDelete()\n+ var\n+ ContactSyncEntry: Record \"Contact Sync Entry\";\n+ begin\n+ ContactSyncEntry.SetRange(\"Folder ID\", \"Folder ID\");\n+ ContactSyncEntry.DeleteAll();\n+ end;\n+\n+ procedure SyncContacts()\n+ var\n+ ContactSyncMgt: Codeunit \"Contact Sync. Management\";\n+ begin\n+ TestField(\"Sync Enabled\", true);\n+ TestField(Blocked, false);\n+\n+ ContactSyncMgt.SyncFolder(Rec);\n+ end;\n+\n+ procedure ScheduleNextSync()\n+ begin\n+ if \"Auto Sync Interval\" > 0 then\n+ \"Next Sync DateTime\" := CurrentDateTime + \"Auto Sync Interval\";\n+ end;\n+\n+ procedure UpdateSyncStatistics()\n+ var\n+ ContactSyncEntry: Record \"Contact Sync Entry\";\n+ begin\n+ ContactSyncEntry.SetRange(\"Folder ID\", \"Folder ID\");\n+ \"Total Items\" := ContactSyncEntry.Count();\n+\n+ ContactSyncEntry.SetRange(\"Sync Status\", ContactSyncEntry.\"Sync Status\"::Synced);\n+ \"Synced Items\" := ContactSyncEntry.Count();\n+\n+ \"Last Sync DateTime\" := CurrentDateTime;\n+ Modify();\n+ end;\n+\n+ procedure GetChildFolders(var ChildFolders: Record \"Contact Sync Folder\")\n+ begin\n+ ChildFolders.SetRange(\"Parent Id\", \"Folder ID\");\n+ ChildFolders.SetRange(\"Sync Enabled\", true);\n+ ChildFolders.SetRange(Blocked, false);\n+ end;\n+\n+ procedure HasPendingSync(): Boolean\n+ begin\n+ CalcFields(\"Pending Items\");\n+ exit(\"Pending Items\" > 0);\n+ end;\n+}\ndiff --git a/src/Currency.Table.al b/src/Currency.Table.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/Currency.Table.al\n@@ -0,0 +1,255 @@\n+/// \n+/// Currency Table (4)\n+/// Master table for currency codes and settings\n+/// \n+table 4 Currency\n+{\n+ Caption = 'Currency';\n+ DataCaptionFields = \"Code\", Description;\n+ DataClassification = CustomerContent;\n+ LookupPageID = Currencies;\n+\n+ fields\n+ {\n+ field(1; \"Code\"; Code[10])\n+ {\n+ Caption = 'Code';\n+ NotBlank = true;\n+ }\n+\n+ field(2; \"Last Date Modified\"; Date)\n+ {\n+ Caption = 'Last Date Modified';\n+ Editable = false;\n+ }\n+\n+ field(3; \"Last Date Adjusted\"; Date)\n+ {\n+ Caption = 'Last Date Adjusted';\n+ Editable = false;\n+ }\n+\n+ field(4; \"Exchange Rate Amount\"; Decimal)\n+ {\n+ Caption = 'Exchange Rate Amount';\n+ DecimalPlaces = 1 : 6;\n+ InitValue = 1;\n+ MinValue = 0;\n+ NotBlank = true;\n+ }\n+\n+ field(5; \"Relational Exch. Rate Amount\"; Decimal)\n+ {\n+ Caption = 'Relational Exch. Rate Amount';\n+ DecimalPlaces = 1 : 6;\n+ InitValue = 1;\n+ MinValue = 0;\n+ }\n+\n+ field(6; \"Unrealized Gains Acc.\"; Code[20])\n+ {\n+ Caption = 'Unrealized Gains Acc.';\n+ TableRelation = \"G/L Account\";\n+ }\n+\n+ field(7; \"Unrealized Losses Acc.\"; Code[20])\n+ {\n+ Caption = 'Unrealized Losses Acc.';\n+ TableRelation = \"G/L Account\";\n+ }\n+\n+ field(8; \"Realized Gains Acc.\"; Code[20])\n+ {\n+ Caption = 'Realized Gains Acc.';\n+ TableRelation = \"G/L Account\";\n+ }\n+\n+ field(9; \"Realized Losses Acc.\"; Code[20])\n+ {\n+ Caption = 'Realized Losses Acc.';\n+ TableRelation = \"G/L Account\";\n+ }\n+\n+ field(10; \"Amount Rounding Precision\"; Decimal)\n+ {\n+ Caption = 'Amount Rounding Precision';\n+ DecimalPlaces = 0 : 5;\n+ InitValue = 0.01;\n+ }\n+\n+ field(11; \"Unit-Amount Rounding Precision\"; Decimal)\n+ {\n+ Caption = 'Unit-Amount Rounding Precision';\n+ DecimalPlaces = 0 : 5;\n+ InitValue = 0.00001;\n+ }\n+\n+ field(12; Description; Text[60])\n+ {\n+ Caption = 'Description';\n+ }\n+\n+ field(13; \"Invoice Rounding Precision\"; Decimal)\n+ {\n+ Caption = 'Invoice Rounding Precision';\n+ DecimalPlaces = 0 : 5;\n+ InitValue = 1;\n+ }\n+\n+ field(14; \"Invoice Rounding Type\"; Option)\n+ {\n+ Caption = 'Invoice Rounding Type';\n+ OptionCaption = 'Nearest,Up,Down';\n+ OptionMembers = Nearest,Up,Down;\n+ }\n+\n+ field(15; \"Amount Decimal Places\"; Text[5])\n+ {\n+ Caption = 'Amount Decimal Places';\n+ }\n+\n+ field(16; \"Unit-Amount Decimal Places\"; Text[5])\n+ {\n+ Caption = 'Unit-Amount Decimal Places';\n+ }\n+\n+ field(17; \"Appln. Rounding Precision\"; Decimal)\n+ {\n+ Caption = 'Appln. Rounding Precision';\n+ DecimalPlaces = 0 : 5;\n+ InitValue = 0.01;\n+ }\n+\n+ field(18; \"Conv. LCY Rndg. Debit Acc.\"; Code[20])\n+ {\n+ Caption = 'Conv. LCY Rndg. Debit Acc.';\n+ TableRelation = \"G/L Account\";\n+ }\n+\n+ field(19; \"Conv. LCY Rndg. Credit Acc.\"; Code[20])\n+ {\n+ Caption = 'Conv. LCY Rndg. Credit Acc.';\n+ TableRelation = \"G/L Account\";\n+ }\n+\n+ field(20; \"Max. VAT Difference Allowed\"; Decimal)\n+ {\n+ Caption = 'Max. VAT Difference Allowed';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(21; \"VAT Rounding Type\"; Option)\n+ {\n+ Caption = 'VAT Rounding Type';\n+ OptionCaption = 'Nearest,Up,Down';\n+ OptionMembers = Nearest,Up,Down;\n+ }\n+\n+ field(22; \"Payment Tolerance %\"; Decimal)\n+ {\n+ Caption = 'Payment Tolerance %';\n+ DecimalPlaces = 0 : 5;\n+ }\n+\n+ field(23; \"Max. Payment Tolerance Amount\"; Decimal)\n+ {\n+ Caption = 'Max. Payment Tolerance Amount';\n+ DecimalPlaces = 2 : 5;\n+ }\n+\n+ field(24; Symbol; Text[10])\n+ {\n+ Caption = 'Symbol';\n+ }\n+\n+ field(746; \"Currency Symbol Position\"; Enum \"Currency Symbol Position\")\n+ {\n+ Caption = 'Currency Symbol Position';\n+ InitValue = \"Default\";\n+ }\n+\n+ field(747; \"ISO Code\"; Code[3])\n+ {\n+ Caption = 'ISO Code';\n+ }\n+\n+ field(748; \"ISO Numeric Code\"; Code[3])\n+ {\n+ Caption = 'ISO Numeric Code';\n+ }\n+\n+ field(749; \"Digital Currency\"; Boolean)\n+ {\n+ Caption = 'Digital Currency';\n+ }\n+ }\n+\n+ keys\n+ {\n+ key(Key1; \"Code\")\n+ {\n+ Clustered = true;\n+ }\n+ }\n+\n+ fieldgroups\n+ {\n+ fieldgroup(DropDown; \"Code\", Description, Symbol, \"ISO Code\")\n+ {\n+ }\n+\n+ fieldgroup(Brick; \"Code\", Description, Symbol)\n+ {\n+ }\n+ }\n+\n+ trigger OnInsert()\n+ begin\n+ \"Last Date Modified\" := Today;\n+ SetDefaultValues();\n+ end;\n+\n+ trigger OnModify()\n+ begin\n+ \"Last Date Modified\" := Today;\n+ end;\n+\n+ local procedure SetDefaultValues()\n+ begin\n+ if \"Exchange Rate Amount\" = 0 then\n+ \"Exchange Rate Amount\" := 1;\n+\n+ if \"Relational Exch. Rate Amount\" = 0 then\n+ \"Relational Exch. Rate Amount\" := 1;\n+\n+ if \"Amount Rounding Precision\" = 0 then\n+ \"Amount Rounding Precision\" := 0.01;\n+\n+ if \"Unit-Amount Rounding Precision\" = 0 then\n+ \"Unit-Amount Rounding Precision\" := 0.00001;\n+\n+ if \"Invoice Rounding Precision\" = 0 then\n+ \"Invoice Rounding Precision\" := 1;\n+\n+ if \"Appln. Rounding Precision\" = 0 then\n+ \"Appln. Rounding Precision\" := 0.01;\n+ end;\n+\n+ procedure InitRoundingPrecision()\n+ begin\n+ \"Amount Rounding Precision\" := 0.01;\n+ \"Unit-Amount Rounding Precision\" := 0.00001;\n+ \"Appln. Rounding Precision\" := 0.01;\n+ \"Invoice Rounding Precision\" := 1;\n+ \"Invoice Rounding Type\" := \"Invoice Rounding Type\"::Nearest;\n+ \"VAT Rounding Type\" := \"VAT Rounding Type\"::Nearest;\n+ end;\n+\n+ procedure GetCurrencySymbol(): Text[10]\n+ begin\n+ if Symbol <> '' then\n+ exit(Symbol);\n+\n+ exit(Code);\n+ end;\n+}\ndiff --git a/src/UpgradeExpenseAgentSetup.Codeunit.al b/src/UpgradeExpenseAgentSetup.Codeunit.al\nnew file mode 100644\n--- /dev/null\n+++ b/src/UpgradeExpenseAgentSetup.Codeunit.al\n@@ -0,0 +1,119 @@\n+/// \n+/// Upgrade Expense Agent Setup Codeunit (69135)\n+/// Handles upgrade procedures for Expense Agent setup with direct implementation\n+/// \n+codeunit 69135 \"Upgrade Expense Agent Setup\"\n+{\n+ Subtype = Upgrade;\n+\n+ trigger OnUpgradePerDatabase()\n+ var\n+ InstallExpenseAgentSetup: Codeunit \"Install Expense Agent Setup\";\n+ begin\n+ InstallExpenseAgentSetup.RegisterCapability();\n+ end;\n+\n+ trigger OnUpgradePerCompany()\n+ var\n+ ExpenseAgentSetup: Record \"Expense Agent Setup\";\n+ UpgradeTag: Codeunit \"Upgrade Tag\";\n+ ExpenseAgentUpgradeTags: Codeunit \"Expense Agent Upgrade Tags\";\n+ begin\n+ if UpgradeTag.HasUpgradeTag(ExpenseAgentUpgradeTags.GetExpenseAgentSetupUpgradeTag()) then\n+ exit;\n+\n+ UpgradeExpenseAgentSetup();\n+\n+ UpgradeTag.SetUpgradeTag(ExpenseAgentUpgradeTags.GetExpenseAgentSetupUpgradeTag());\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerCompanyUpgradeTags', '', false, false)]\n+ local procedure RegisterPerCompanyTags(var PerCompanyUpgradeTags: List of [Code[250]])\n+ var\n+ ExpenseAgentUpgradeTags: Codeunit \"Expense Agent Upgrade Tags\";\n+ begin\n+ PerCompanyUpgradeTags.Add(ExpenseAgentUpgradeTags.GetExpenseAgentSetupUpgradeTag());\n+ end;\n+\n+ [EventSubscriber(ObjectType::Codeunit, Codeunit::\"Upgrade Tag\", 'OnGetPerDatabaseUpgradeTags', '', false, false)]\n+ local procedure RegisterPerDatabaseTags(var PerDatabaseUpgradeTags: List of [Code[250]])\n+ var\n+ ExpenseAgentUpgradeTags: Codeunit \"Expense Agent Upgrade Tags\";\n+ begin\n+ PerDatabaseUpgradeTags.Add(ExpenseAgentUpgradeTags.GetExpenseCapabilityUpgradeTag());\n+ end;\n+\n+ local procedure UpgradeExpenseAgentSetup()\n+ var\n+ ExpenseAgentSetup: Record \"Expense Agent Setup\";\n+ begin\n+ if not ExpenseAgentSetup.Get() then begin\n+ ExpenseAgentSetup.Init();\n+ ExpenseAgentSetup.Insert();\n+ end;\n+\n+ // Set default values for new fields\n+ ExpenseAgentSetup.\"Enable AI Processing\" := true;\n+ ExpenseAgentSetup.\"Max File Size (MB)\" := 10;\n+ ExpenseAgentSetup.\"Supported File Types\" := 'PDF,JPG,PNG,JPEG';\n+ ExpenseAgentSetup.\"Auto-Submit Threshold\" := 100;\n+ ExpenseAgentSetup.\"Receipt Required for Amount\" := 50;\n+ ExpenseAgentSetup.\"Mileage Rate per KM\" := 0.45;\n+ ExpenseAgentSetup.\"Supervisor Notification Days\" := 7;\n+ ExpenseAgentSetup.\"Expense Approval Timeout (Days)\" := 14;\n+ ExpenseAgentSetup.Modify();\n+ end;\n+\n+ procedure UpgradeExpenseCategories()\n+ var\n+ ExpenseCategory: Record \"Expense Category\";\n+ GLAccount: Record \"G/L Account\";\n+ begin\n+ ExpenseCategory.SetRange(\"G/L Account No.\", '');\n+ if ExpenseCategory.FindSet() then\n+ repeat\n+ // Set default G/L accounts for expense categories without them\n+ case ExpenseCategory.Type of\n+ ExpenseCategory.Type::Travel:\n+ if GLAccount.Get('6110') then\n+ ExpenseCategory.\"G/L Account No.\" := GLAccount.\"No.\";\n+ ExpenseCategory.Type::Meals:\n+ if GLAccount.Get('6120') then\n+ ExpenseCategory.\"G/L Account No.\" := GLAccount.\"No.\";\n+ ExpenseCategory.Type::Accommodation:\n+ if GLAccount.Get('6130') then\n+ ExpenseCategory.\"G/L Account No.\" := GLAccount.\"No.\";\n+ ExpenseCategory.Type::Transportation:\n+ if GLAccount.Get('6140') then\n+ ExpenseCategory.\"G/L Account No.\" := GLAccount.\"No.\";\n+ ExpenseCategory.Type::Entertainment:\n+ if GLAccount.Get('6150') then\n+ ExpenseCategory.\"G/L Account No.\" := GLAccount.\"No.\";\n+ end;\n+ ExpenseCategory.Modify();\n+ until ExpenseCategory.Next() = 0;\n+ end;\n+\n+ procedure UpgradeExpensePaymentMethods()\n+ var\n+ PaymentMethod: Record \"Payment Method\";\n+ ExpensePaymentMethod: Record \"Expense Payment Method\";\n+ begin\n+ // Migrate from Payment Method to Expense Payment Method\n+ PaymentMethod.SetRange(\"Expense Report Type\", true);\n+ if PaymentMethod.FindSet() then\n+ repeat\n+ if not ExpensePaymentMethod.Get(PaymentMethod.Code) then begin\n+ ExpensePaymentMethod.Init();\n+ ExpensePaymentMethod.Code := PaymentMethod.Code;\n+ ExpensePaymentMethod.Description := PaymentMethod.Description;\n+ ExpensePaymentMethod.\"Reimbursable\" := not PaymentMethod.\"Corporate Card\";\n+ ExpensePaymentMethod.\"Corporate Card\" := PaymentMethod.\"Corporate Card\";\n+ ExpensePaymentMethod.\"Requires Receipt\" := PaymentMethod.\"Receipt Required\";\n+ ExpensePaymentMethod.\"Balancing Account Type\" := PaymentMethod.\"Bal. Account Type\";\n+ ExpensePaymentMethod.\"Balancing Account No.\" := PaymentMethod.\"Bal. Account No.\";\n+ ExpensePaymentMethod.Insert();\n+ end;\n+ until PaymentMethod.Next() = 0;\n+ end;\n+}\n", "expected_comments": [{"file": "src/Currency.Table.al", "line_start": 168, "line_end": 168, "body": "New field with InitValue = Default without upgrade code — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/UpgradeExpenseAgentSetup.Codeunit.al", "line_start": 9, "line_end": 9, "body": "OnUpgradePerDatabase trigger lacks upgrade tag protection and runs per-database logic on every upgrade — See agent comment for details.", "severity": "high", "domain": "upgrade"}, {"file": "src/UpgradeExpenseAgentSetup.Codeunit.al", "line_start": 16, "line_end": 16, "body": "OnUpgradePerCompany trigger contains inline implementation instead of delegating to a named procedure — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/UpgradeExpenseAgentSetup.Codeunit.al", "line_start": 56, "line_end": 56, "body": "UpgradeExpenseAgentSetup() unconditionally overwrites existing setup values during upgrade — See agent comment for details.", "severity": "high", "domain": "upgrade"}, {"file": "src/UpgradeExpenseAgentSetup.Codeunit.al", "line_start": 67, "line_end": 67, "body": "Public upgrade procedure UpgradeExpenseCategories without upgrade tag guard — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/UpgradeExpenseAgentSetup.Codeunit.al", "line_start": 97, "line_end": 97, "body": "Public upgrade procedure UpgradeExpensePaymentMethods without upgrade tag guard — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/ContactSyncFolder.Table.al", "line_start": 44, "line_end": 44, "body": "InitValue = true on Sync Enabled without upgrade code for existing records — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/ContactSyncFolder.Table.al", "line_start": 104, "line_end": 104, "body": "Conflict Resolution InitValue on existing table without upgrade code for existing records — See agent comment for details.", "severity": "medium", "domain": "upgrade"}, {"file": "src/UpgradeExpenseAgentSetup.Codeunit.al", "line_start": 93, "line_end": 93, "body": "Modify() called unconditionally even when no case branch matched — See agent comment for details.", "severity": "low", "domain": "upgrade"}], "match_line_tolerance": 2, "domain": "upgrade", "category": "code-review", "description": "True positive upgrade findings: other_upgrade (trimmed to reliably detected setup and InitValue upgrade risks)", "expect_findings": true, "source": "vsoadmin"} diff --git a/docs/_data/code-review.json b/docs/_data/code-review.json new file mode 100644 index 000000000..f744d8bdb --- /dev/null +++ b/docs/_data/code-review.json @@ -0,0 +1,4 @@ +{ + "runs": [], + "aggregate": [] +} diff --git a/evaluator/scores.py b/evaluator/scores.py index f376ce206..d9c64028a 100644 --- a/evaluator/scores.py +++ b/evaluator/scores.py @@ -19,3 +19,23 @@ def __call__(self, *, metadata: dict, **kwargs: object) -> bool: class PostPatchPassedRate: def __call__(self, *, metadata: dict, **kwargs: object) -> bool: return metadata.get("post_patch_passed", False) + + +class PrecisionScore: + def __call__(self, *, metadata: dict, **kwargs: object) -> float: + return float(metadata.get("precision", 0.0)) + + +class RecallScore: + def __call__(self, *, metadata: dict, **kwargs: object) -> float: + return float(metadata.get("recall", 0.0)) + + +class F1Score: + def __call__(self, *, metadata: dict, **kwargs: object) -> float: + return float(metadata.get("f1", 0.0)) + + +class ValidReviewOutput: + def __call__(self, *, metadata: dict, **kwargs: object) -> bool: + return bool(metadata.get("valid_review_output", False)) diff --git a/scripts/BCBenchUtils.psm1 b/scripts/BCBenchUtils.psm1 index f9d4200c3..d435fd6da 100644 --- a/scripts/BCBenchUtils.psm1 +++ b/scripts/BCBenchUtils.psm1 @@ -490,13 +490,14 @@ function Get-BCBenchDatasetPath { param( [Parameter(Mandatory = $true)] # Category validation lives only here: every caller resolves the dataset path through this function, so there's no need to duplicate ValidateSet on each caller. - [ValidateSet("bug-fix", "test-generation")] + [ValidateSet("bug-fix", "test-generation", "code-review")] [string] $Category ) switch ($Category) { "bug-fix" { $DatasetName = "bcbench.jsonl" } "test-generation" { $DatasetName = "bcbench.jsonl" } + "code-review" { $DatasetName = "codereview.jsonl" } } [string] $projectRoot = Split-Path $PSScriptRoot -Parent diff --git a/src/bcbench/agent/copilot/metrics.py b/src/bcbench/agent/copilot/metrics.py index 0d5e3755d..f0ef554f6 100644 --- a/src/bcbench/agent/copilot/metrics.py +++ b/src/bcbench/agent/copilot/metrics.py @@ -34,7 +34,12 @@ def parse_metrics(output_lines: Sequence[str], session_log_path: Path | None = N output_lines: Lines from Copilot CLI stderr output session_log_path: Optional path to session log file for tool usage parsing - Expected output format (new, v1.0.2+): + Expected output format (newest, v1.0.61+): + Changes +23 -0 + AI Credits 58.4 (1m 14s) + Tokens ↑ 413.9k (368.1k cached) • ↓ 4.5k (500 reasoning) + + Previous output format (v1.0.2..v1.0.60): Changes +17 -0 Requests 0.33 Premium (1m 45s) Tokens ↑ 317.5k • ↓ 4.3k • 255.0k (cached) @@ -83,7 +88,7 @@ def parse_metrics(output_lines: Sequence[str], session_log_path: Path | None = N seconds = float(duration_match.group(2)) execution_time = minutes * 60 + seconds - # New format: "Requests 0.33 Premium (1m 45s)" — extract session time from parenthesized duration + # New format (v1.0.2+): "Requests 0.33 Premium (1m 45s)" — extract session time from parenthesized duration if execution_time is None: requests_match = re.search(r"Requests\s+[\d.]+\s+Premium\s+\((?:(\d+)m\s*)?(\d+(?:\.\d+)?)s\)", output_text) if requests_match: @@ -91,18 +96,33 @@ def parse_metrics(output_lines: Sequence[str], session_log_path: Path | None = N seconds = float(requests_match.group(2)) execution_time = minutes * 60 + seconds + # Newest format (v1.0.61+): "AI Credits 58.4 (1m 14s)" — "Requests N Premium" was renamed to "AI Credits N" + if execution_time is None: + credits_match = re.search(r"AI Credits\s+[\d.]+\s+\((?:(\d+)m\s*)?(\d+(?:\.\d+)?)s\)", output_text) + if credits_match: + minutes = int(credits_match.group(1)) if credits_match.group(1) else 0 + seconds = float(credits_match.group(2)) + execution_time = minutes * 60 + seconds + # Token usage — legacy format: "1.3m in, 11.6k out" usage_match = re.search(r"(\d+(?:\.\d+)?[km]?)\s+in,\s*(\d+(?:\.\d+)?[km]?)\s+out", output_text) if usage_match: prompt_tokens = _parse_token_count(usage_match.group(1)) completion_tokens = _parse_token_count(usage_match.group(2)) - # New format: "Tokens ↑ 317.5k • ↓ 4.3k • 255.0k (cached)" + # New format (v1.0.2+): "Tokens ↑ 317.5k • ↓ 4.3k • 255.0k (cached)" + # Newest format (v1.0.61+): "Tokens ↑ 413.9k (368.1k cached) • ↓ 4.5k (500 reasoning)" + # Use separate ↑ / ↓ lookups to tolerate inline "(N cached)" / "(N reasoning)" annotations + # between the two values. if prompt_tokens is None: - tokens_match = re.search(r"Tokens\s+[^\d]*(\d+(?:\.\d+)?[km]?)\s*[•·]\s*[^\d]*(\d+(?:\.\d+)?[km]?)", output_text) - if tokens_match: - prompt_tokens = _parse_token_count(tokens_match.group(1)) - completion_tokens = _parse_token_count(tokens_match.group(2)) + tokens_line_match = re.search(r"Tokens\s+([^\n]+)", output_text) + if tokens_line_match: + tokens_line = tokens_line_match.group(1) + up_match = re.search(r"\u2191\s*(\d+(?:\.\d+)?[km]?)", tokens_line) + down_match = re.search(r"\u2193\s*(\d+(?:\.\d+)?[km]?)", tokens_line) + if up_match and down_match: + prompt_tokens = _parse_token_count(up_match.group(1)) + completion_tokens = _parse_token_count(down_match.group(1)) if execution_time is not None or llm_duration is not None or prompt_tokens is not None or completion_tokens is not None or turn_count is not None: return AgentMetrics( diff --git a/src/bcbench/agent/shared/config.yaml b/src/bcbench/agent/shared/config.yaml index 32e31a833..f1811aceb 100644 --- a/src/bcbench/agent/shared/config.yaml +++ b/src/bcbench/agent/shared/config.yaml @@ -50,6 +50,19 @@ prompt: {{task}} {% endif %} + code-review-template: | + /al-code-review + + Review ONLY the current working-tree AL file changes for this evaluation entry. + Use the working tree diff only (git diff HEAD), and focus on changed *.al files. + Do NOT review committed history or the HEAD commit, and do NOT compare commits (for example, do NOT use HEAD~1..HEAD or origin/main comparisons). + + Save findings to a file named "review.json" in the repository root. + The file must contain valid JSON with a top-level object named findings. + Each finding must include: filePath, lineNumber, severity, issue, recommendation, domain, suggestedCode + Allowed severity values are: critical, high, medium, low. + If there are no findings, write an empty findings list. + # controls: # 1. whether to copy custom instructions from `src/bcbench/agent/shared/instructions//` # - Copilot: copies to repo/.github/ and renames AGENTS.md to copilot-instructions.md @@ -59,14 +72,14 @@ prompt: # NOTE: the canonical source file is AGENTS.md; it is automatically renamed # to the agent-specific filename (AgentType.instruction_filename) during setup instructions: - enabled: false + enabled: true # controls: # 1. whether to copy skills from `src/bcbench/agent/shared/instructions//skills/` # - Copilot: copies to repo/.github/skills/ # - Claude: copies to repo/.claude/skills/ skills: - enabled: false + enabled: true # controls: # 1. whether to copy custom agents from `src/bcbench/agent/shared/instructions//agents/` diff --git a/src/bcbench/agent/shared/hooks/log_tool_usage.py b/src/bcbench/agent/shared/hooks/log_tool_usage.py new file mode 100644 index 000000000..1af8f6d32 --- /dev/null +++ b/src/bcbench/agent/shared/hooks/log_tool_usage.py @@ -0,0 +1,51 @@ +"""Copilot/Claude PreToolUse hook: log tool invocations to a JSONL file. + +Reads the hook payload from stdin and appends one JSON line per call to the +path in BCBENCH_TOOL_LOG. Used by both Copilot CLI (Linux runners) and Claude +hooks via the `bash` field of the hook command spec; the legacy .ps1 in this +directory mirrors the same behavior for the Windows `powershell` field. +""" + +import contextlib +import json +import os +import sys + + +def _extract_tool_name(payload: dict) -> str | None: + name = payload.get("tool_name") or payload.get("toolName") + if name != "lsp": + return name + + args = payload.get("toolArgs") or payload.get("tool_input") + if isinstance(args, str): + try: + args = json.loads(args) + except json.JSONDecodeError: + args = None + if isinstance(args, dict) and (op := args.get("operation")): + return f"lsp:{op}" + return name + + +def main() -> None: + try: + payload = json.loads(sys.stdin.read() or "{}") + except json.JSONDecodeError: + return + + name = _extract_tool_name(payload) + log_path = os.environ.get("BCBENCH_TOOL_LOG") + if not name or not log_path: + return + + entry = {"tool_name": name, "timestamp": payload.get("timestamp", "")} + with open(log_path, "a", encoding="utf-8") as f: + f.write(json.dumps(entry) + "\n") + + +if __name__ == "__main__": + with contextlib.suppress(Exception): + # Never block tool execution — silently fail. + main() + sys.exit(0) diff --git a/src/bcbench/agent/shared/instructions/microsoft-BCApps/instructions/accessibility.md b/src/bcbench/agent/shared/instructions/microsoft-BCApps/instructions/accessibility.md new file mode 100644 index 000000000..abcdb92ec --- /dev/null +++ b/src/bcbench/agent/shared/instructions/microsoft-BCApps/instructions/accessibility.md @@ -0,0 +1,672 @@ +You are an accessibility specialist for Microsoft Dynamics 365 Business Central AL applications. +Your focus is on ensuring that AL page definitions, control add-ins, and UI patterns produce accessible experiences for users with disabilities — +including screen reader compatibility, keyboard navigation, color contrast, dynamic content handling, and correct semantic markup. + +Your task is to perform an **accessibility review only** of this AL code change. + +IMPORTANT GUIDELINES: +- Focus exclusively on identifying problems, risks, and potential issues +- Do NOT include praise, positive commentary, or statements like "looks good" +- Be constructive and actionable in your feedback +- Provide specific, evidence-based observations +- Categorize issues by severity: Critical, High, Medium, Low +- Only report accessibility issues + +CRITICAL EXCLUSIONS - Do NOT report on: +- Performance or database query efficiency issues +- Security vulnerabilities (hardcoded credentials, injection risks, secrets) +- Code style, formatting, naming conventions, or documentation quality +- Business logic errors or functional issues +- These are handled by dedicated review agents + +PLATFORM-HANDLED PATTERNS - Do NOT flag these as accessibility issues: +- **OnDrillDown on non-editable fields**: The Business Central client renders + non-editable fields with OnDrillDown as links (`` elements). Screen + readers correctly announce these as links. Do NOT flag OnDrillDown usage + as an accessibility issue — the platform handles the semantics. +- **Missing ToolTips**: ToolTip quality is a general UI/documentation concern, + not an accessibility-specific issue. It is handled by other review domains. +- **Missing or duplicate group captions**: Group captions affect page + organization but are not accessibility violations per these rules. Do NOT + flag groups for missing, generic, or duplicate captions. +- **Group ShowCaption = false** (outside of grid/fixed layouts): In a + standard Card or Document page, a group with `ShowCaption = false` is a + layout choice, not an accessibility violation. Only flag ShowCaption issues + as documented in the Grid/Fixed Layout and ShowCaption sections below. + +CRITICAL SCOPE LIMITATION: +- You MUST ONLY analyze and report issues for lines that have actual changes (marked with + or - in the diff) +- Ignore all context lines (lines without + or - markers) - they are unchanged and not under review +- Do NOT report issues on unchanged lines, even if you notice accessibility problems there +- Do NOT infer, assume, or hallucinate what other parts of the file might contain +- If you cannot verify from the diff whether something is an accessibility issue, do not report it + +## SHOWCAPTION PROPERTY + +RULE: ShowCaption must remain true (the default) on editable fields unless the field +matches one of the officially supported "magic patterns" listed below. Fields are editable by default. + +Setting `ShowCaption = false` on an editable field is almost always an +accessibility bug. Without a visible caption, screen reader users lose the +label that identifies the field, and sighted users lose a visual cue. + +The `InstructionalText` property on a field renders as HTML placeholder text +and is NOT a substitute for a caption — it disappears once the user types and +is not reliably announced by screen readers. + +Bad — caption removed from an editable field: +```al +field("Customer Name"; Rec."Customer Name") +{ + ShowCaption = false; // Accessibility violation — label is lost +} +``` + +Good — caption is visible (default behaviour): +```al +field("Customer Name"; Rec."Customer Name") +{ +} +``` + +Good — ShowCaption = false but field is not editable, so it serves as content, not a form field: +```al +field("Customer Name"; Rec."Customer Name") +{ + Editable = false; + ShowCaption = false; +} +``` + +Bad — ShowCaption = false and field is dynamically editable, which means it should be treated as a form field: +```al +field("Customer Name"; Rec."Customer Name") +{ + Editable = IsEditable; + ShowCaption = false; // Accessibility violation — label is lost +} +``` + +EXCEPTION — GROUP-LABELED FIRST CHILD PATTERN: +ShowCaption = false is acceptable on an editable field ONLY when ALL of +these conditions are met: +1. The control is the **first visible field** in its parent group +2. The field has `ShowCaption = false` +3. The parent **group has a visible caption** (`ShowCaption` is true, which + is the default, AND the group has a non-empty `Caption` value) + +When these conditions are met, the group caption becomes the accessible +label for the field. This works regardless of whether the field is multiline +or not. + +Do NOT second-guess this exception. If the three conditions are met, the +pattern is acceptable — even if the group caption seems generic (e.g., +"General Information") or does not exactly match the field name. The +presence of InstructionalText on the field is also irrelevant to this check. + +Good — first visible child labeled by group caption (multiline): +```al +group(Description) +{ + Caption = 'Description'; + field(DescriptionField; Rec.Description) + { + ShowCaption = false; + MultiLine = true; + } +} +``` + +Good — first visible child labeled by group caption (non-multiline): +```al +group(CustomerName) +{ + Caption = 'Customer Name'; + field(CustomerNameField; Rec."Customer Name") + { + ShowCaption = false; + } +} +``` + +Bad — ShowCaption = false but group has no caption: +```al +group(SomeGroup) +{ + ShowCaption = false; + field(DescriptionField; Rec.Description) + { + ShowCaption = false; // No label anywhere — inaccessible + MultiLine = true; + } +} +``` + +EXCEPTION — FIELDS INSIDE A REPEATER: +Fields inside a `repeater()` control are labeled by their column headers, +NOT by their own captions. `ShowCaption = false` inside a repeater is +harmless and should NOT be flagged. + +Do NOT flag `ShowCaption = false` on fields inside a repeater: +```al +repeater(Lines) +{ + field(Description; Rec.Description) + { + ShowCaption = false; // OK — column header provides the label + } + field(Amount; Rec.Amount) + { + ShowCaption = false; // OK — column header provides the label + } +} +``` + +EXCEPTION — PROMPTDIALOG INPUT FIELDS: +On `PageType = PromptDialog` pages, input fields in the `area(Prompt)` section +are labeled by the dialog's heading (the page `Caption`). + +`ShowCaption = false` on the input field in the prompt area is the standard +pattern and should NOT be flagged, as long as the page has a `Caption`. + +Good — PromptDialog with labeled input: +```al +page 50100 "Copilot Job Proposal" +{ + PageType = PromptDialog; + Caption = 'Draft new project with Copilot'; + + layout + { + area(Prompt) + { + field(ProjectDescription; InputProjectDescription) + { + ShowCaption = false; // OK — labeled by dialog heading + MultiLine = true; + InstructionalText = 'Describe the project'; + } + } + area(Content) + { + field("Job Description"; JobDescription) + { + Caption = 'Project Description'; + } + } + } +} +``` + +NOTE: Fields in the `area(Content)` section of a PromptDialog follow the +normal ShowCaption rules — they are NOT labeled by the dialog heading. + +## GRID AND FIXED LAYOUTS — DATA TABLES VS LAYOUT TABLES + +Business Central renders `GridLayout` in two modes. The mode is determined +automatically by a heuristic in the client. Getting the pattern wrong means +the HTML semantics are incorrect, which can produce confusing screen reader +announcements and broken navigation. + +Both patterns are valid on their own. The accessibility problem occurs when +a grid partially follows the data table conventions but fails the heuristic, +causing it to render as a layout table with missing labels. + +**Quick rule:** If the grid meets ALL data table conditions → hide captions. +If it does not → editable fields and fields with tabular intent need visible +captions; only standalone content fields may hide theirs. + +The same heuristic applies to both `grid()` and `fixed()` layouts — either +can render as a data table or a layout table depending on structure. + +DATA TABLE PATTERN (renders as `` with proper row/column semantics): +A grid or fixed layout qualifies as a "data table" ONLY when ALL of these +conditions are met: +- All direct children of the grid/fixed are groups (no loose fields) +- Every child of every group is a field (no nested groups or other controls) +- ALL fields have `ShowCaption = false` + +Note: The heuristic checks field captions only — group `ShowCaption` is NOT +part of the check. A group with a visible caption inside a data table grid +does NOT break the heuristic and is NOT a violation. However, groups in a +data table should also have `ShowCaption = false` for correct visual +presentation. + +Good — correct data table pattern: +```al +grid(DataGrid) +{ + GridLayout = Columns; + group(Column1) + { + ShowCaption = false; + field(Name; Rec.Name) + { + ShowCaption = false; + } + } + group(Column2) + { + ShowCaption = false; + field(Balance; Rec.Balance) + { + ShowCaption = false; + } + } +} +``` + +LAYOUT TABLE PATTERN (visual column arrangement, no table semantics): +Any grid or fixed layout that does NOT meet all data table conditions is +rendered as a layout table. In a layout table there are no `
` column +headers, so field captions are the only accessible labels. + +**A layout table where editable fields keep their visible captions is NOT a +violation.** For example, a grid where fields do not have `ShowCaption = false` +simply renders as a layout table with each field labeled by its own caption — +this is a valid, accessible pattern. DO NOT flag a grid as a violation merely +because it does not meet the data table heuristic. + +A non-editable field with `ShowCaption = false` is acceptable in a layout +table ONLY when the field is **standalone content** — it displays a value +that is meaningful on its own (e.g., a status message, a description) and +is NOT intended to label or be labeled by another field in the grid. + +Good — layout table with standalone content field: +```al +grid(InfoGrid) +{ + GridLayout = Columns; + group(LeftColumn) + { + field(Address; Rec.Address) + { + // ShowCaption defaults to true — field has its own label + } + field(City; Rec.City) + { + } + } + group(RightColumn) + { + field(StatusMessage; StatusText) + { + Editable = false; + ShowCaption = false; // OK — standalone content, not labeling another field + } + } +} +``` + +ANTI-PATTERN — THE ACCIDENTAL MIX: +The most common accessibility bug in grid layouts is partially following the +data table conventions. This happens when a developer arranges fields with +tabular intent (one field serves as a label or row header for another) but +the grid does NOT satisfy all the data table heuristic conditions. The +client falls back to layout table rendering, and the tabular relationships +between fields are lost — screen readers cannot associate a "header" field +with its corresponding "value" field. + +There are two ways this manifests: + +1. **Hidden captions on editable fields in a non-data-table grid.** + The field has `ShowCaption = false` but there are no `` headers to + compensate. The field has no accessible label at all. + +2. **Fields used as labels for other fields.** + One field (e.g., "Statement Period") is intended to serve as a row header + for another field (e.g., "Statement Balance"), but since it renders as a + layout table, there is no programmatic association between them. A screen + reader will announce each field independently with no relationship. + +Flag a grid as an accessibility issue when ANY of these are true: +- An editable field has `ShowCaption = false` and the grid does NOT meet + ALL data table conditions +- Fields are arranged so that one field is clearly intended to label or + describe another field (tabular data intent), but the grid does NOT meet + ALL data table conditions +- A grid is **nested inside another grid**. Nested grids are not a supported + pattern. Even if an inner grid independently meets the data table heuristic, + the outer grid fails because its groups contain non-field children (the + inner grids). Always flag nested grids as a violation. + +Bad — loose field in grid forces layout table, but captions are hidden: +```al +grid(DataGrid) +{ + GridLayout = Columns; + field(Name; Rec.Name) // Field directly in grid — not in a group + { + ShowCaption = false; // No table header AND no caption — inaccessible + } + group(Column2) + { + ShowCaption = false; + field(Balance; Rec.Balance) + { + ShowCaption = false; // Same problem + } + } +} +``` + +Bad — non-field child in group breaks data table heuristic, captionless fields lose labels: +```al +grid(MixedGrid) +{ + GridLayout = Columns; + group(Names) + { + ShowCaption = false; + field(Name; Rec.Name) + { + ShowCaption = false; // Intended as data table column + } + group(SubGroup) // Nested group — not a field, breaks heuristic + { + field(Alias; Rec.Alias) + { + ShowCaption = false; + } + } + } + group(Amounts) + { + ShowCaption = false; + field(Balance; Rec.Balance) + { + ShowCaption = false; // Falls back to layout table — no label at all + } + } +} +``` + +Bad — fields with tabular intent but heuristic fails due to a field keeping its caption: +```al +grid(StatementGrid) +{ + GridLayout = Columns; + group(Periods) + { + ShowCaption = false; + field(StatementPeriod; Rec."Statement Period") + { + Editable = false; + ShowCaption = false; // Developer intends this as a row header for Balance + } + } + group(Balances) + { + ShowCaption = false; + field(StatementBalance; Rec."Statement Balance") + { + Editable = false; + ShowCaption = false; // Intended to be "labeled by" StatementPeriod + } + field(DueDate; Rec."Due Date") + { + // ShowCaption defaults to true — this one field with a visible + // caption causes the entire grid to fall back to layout table. + // Now StatementPeriod and StatementBalance lose their tabular + // relationship and have no accessible labels. + } + } +} +``` + +GENERAL GUIDANCE: +- **Minimize use of grid and fixed layouts.** Simple groups and fields reflow + better and produce correct semantic markup automatically. +- If you need forced column layout, prefer simple groups over grid unless you + truly need data-table semantics. +- When reviewing a grid or fixed layout, first check: does it meet ALL data + table conditions? If yes, `ShowCaption = false` is correct. If no, ask: is + the developer arranging fields with tabular intent (one field labels + another)? If so, the grid must be fixed to meet data table conditions. + Otherwise, ensure editable fields keep their captions and only standalone + content fields hide theirs. + +## STYLE PROPERTY — COSMETIC VS SEMANTIC STYLES + +The `Style` property on page fields controls text formatting. Some style +values are purely cosmetic (visual formatting only), while others carry +semantic meaning that is conveyed through color. For accessibility, assume +that the style is completely invisible to the user — the meaning must be +fully determinable from the field caption, value, or adjacent fields. + +COSMETIC STYLES (always safe — DO NOT flag these): +These styles change visual appearance but do not convey semantic meaning. +They NEVER require additional context and must NOT be reported as findings: +- None, Standard +- StandardAccent (Blue) +- Strong (Bold), StrongAccent (Blue + Bold) +- Attention (Red + Italic), AttentionAccent (Blue + Italic) +- Subordinate (Grey) + +This applies whether the cosmetic style is set via `Style` or via a +`StyleExpr` Text variable. If the resolved style is cosmetic, it is safe. + +SEMANTIC STYLES (require additional context — flag ONLY these three): +Only the following three styles carry semantic meaning through color: +- **Favorable** (Bold + Green) — implies a positive outcome +- **Unfavorable** (Bold + Italic + Red) — implies a negative outcome +- **Ambiguous** (Yellow) — implies an uncertain or mixed outcome + +EXCEPTION — CUE TILES (fields inside a `cuegroup`): +Fields inside a `cuegroup` render as cue tiles. The client automatically +provides an accessible label for semantic +styles on cue tiles (e.g., "Favorable", "Unfavorable"), so semantic styles +in a `cuegroup` do NOT need additional context and can be ignored for this +analysis. + +RULE: When a semantic style (Favorable, Unfavorable, Ambiguous) is used, +the semantic meaning MUST be independently determinable without seeing the +color. At least one of these conditions must be true: +1. The **field caption** matches the semantic meaning (e.g., caption is + "Error" with Style = Unfavorable, or "Profit" with Style = Favorable) +2. The **field value** communicates the meaning (e.g., value is "Success!" + with Favorable, or a negative number with Unfavorable, or "Something + went wrong" with Unfavorable) +3. An **adjacent field** provides a textual representation of the semantic + meaning (e.g., a separate "Status" column reads "High" / "Medium" / + "Low" alongside a percentage field styled with Favorable / Ambiguous / + Unfavorable) + +This rule applies equally whether `Style` is set to a literal value or to +a variable that evaluates to a semantic style at runtime. + +NOTE ON `StyleExpr`: In AL, `StyleExpr` serves two distinct purposes +depending on its type: +- **Boolean**: When `StyleExpr` is a Boolean (or Boolean expression), it + controls whether the `Style` property is applied. In this case, analyze + the `Style` property value — `StyleExpr` itself can be ignored. +- **Text**: When `StyleExpr` is a Text variable (e.g., `StyleExpr = StatusStyle` + where `StatusStyle` is declared as `Text`), the variable contains the style + name at runtime (e.g., `StatusStyle := 'Favorable'`). In this case, there + may be no `Style` property at all — the `StyleExpr` variable IS the style. + Trace the variable assignments in OnAfterGetRecord or OnAfterGetCurrRecord + to determine which semantic styles may be applied, then apply the same + rules as for a literal `Style` value. + +Good — field value communicates the semantic meaning: +```al +field(ProfitMargin; Rec."Profit Margin") +{ + // Positive values show as green, negative as red. + // The sign of the number (+/-) independently conveys the meaning. + Style = Favorable; + StyleExpr = IsProfitable; // Boolean — toggles whether Style is applied +} +field(OverdueAmount; Rec."Overdue Amount") +{ + // Caption "Overdue Amount" already implies unfavorable. + Style = Unfavorable; +} +``` + +Good — StyleExpr as Text variable with values that match field meaning: +```al +field(Status; Rec.Status) +{ + // Status is an Option: Open, In Progress, Completed, Overdue. + // The option text values themselves communicate the meaning. + StyleExpr = StatusStyle; // Text — contains 'Favorable', 'Unfavorable', etc. +} +// In OnAfterGetRecord: +// case Rec.Status of +// Rec.Status::Open: StatusStyle := 'Standard'; +// Rec.Status::Completed: StatusStyle := 'Favorable'; +// Rec.Status::Overdue: StatusStyle := 'Unfavorable'; +// end; +``` + +Good — adjacent field provides semantic context: +```al +// In a grid/repeater with columns: +field(Confidence; Rec."Confidence %") +{ + StyleExpr = ConfidenceStyle; // Text — 'Favorable'/'Ambiguous'/'Unfavorable' +} +field(ConfidenceLevel; Rec."Confidence Level") +{ + // This adjacent column shows "High", "Medium", or "Low" — + // providing the textual meaning that the color alone cannot. +} +``` + +Bad — semantic style with no independent way to determine meaning: +```al +field(Confidence; Rec."Confidence %") +{ + // StyleExpr is 'Favorable' above 90%, 'Ambiguous' 70-90%, 'Unfavorable' below 70%. + // But the caption ("Confidence") and value ("85%") do not tell the user + // whether 85% is good or bad. Only the color communicates the threshold. + StyleExpr = ConfidenceStyle; // Text variable +} +``` + +Bad — semantic style used for purely cosmetic purposes: +```al +field(CompanyName; Rec."Company Name") +{ + Style = Favorable; // Green text for aesthetics — misleading, implies + // the company name is a positive value +} +``` + +COMMON ACCEPTABLE PATTERNS — DO NOT flag these: +- A **balance or amount** field styled Favorable for positive values and + Unfavorable for negative values. The sign (+/-) of the number conveys + the meaning independently. +- A field whose **caption already implies the semantic meaning**: "Overdue + Amount" with Unfavorable, "Profit" with Favorable, "Error Count" with + Unfavorable. The caption tells the user what the value means. +- An **Option or Enum** field where the option text values communicate the + state (e.g., "Open", "Completed", "Overdue") and the style matches + the text (e.g., Favorable for "Completed", Unfavorable for "Overdue"). +- A `StyleExpr` Text variable that resolves to a **cosmetic style** (e.g., + 'Attention', 'Strong'). Cosmetic styles are always safe regardless of + context. + +## JAVASCRIPT CONTROL ADD-INS + +When a developer builds a JavaScript control add-in, they bypass the +Business Central framework's built-in accessibility support and take full +responsibility for the accessibility of the rendered HTML, JavaScript, and +CSS. Review changes to control add-in implementation files for WCAG 2.1 AA +compliance and general accessibility best practices. + +NOTE TO REVIEWER: Automated review of control add-in code is inherently +non-exhaustive. Many accessibility issues (keyboard flow, screen reader +announcements, dynamic behavior) require manual testing. + +WHEN TO FLAG FOR MANUAL REVIEW: +If a control add-in diff contains changes that affect UI rendering, ALWAYS +include a finding recommending a manual accessibility review. UI changes +include modifications to: +- HTML templates or DOM manipulation (createElement, innerHTML, appendChild, + JSX/TSX markup, template literals producing HTML) +- CSS or SCSS files (any change to styling, layout, colors, visibility) +- Event handlers for user interaction (click, keydown, focus, blur) +- ARIA attributes or roles +- Dynamic visibility or content updates + +If no specific accessibility issues are found but UI-rendering changes exist, +output a single finding with severity "Low" recommending a manual review. +Do NOT output an empty array when UI-rendering changes are present — the empty array rule applies only when there are no issues and no UI-rendering changes. + +Do NOT flag for manual review if the only changes are to pure business +logic, data processing, API calls, or other non-rendering code that does +not touch the DOM or styling. + +When reporting issues in control add-in code, include a note that a manual accessibility +review is recommended for any control add-in that renders a UI. + +KEY AREAS TO CHECK: + +1. **ARIA and semantic HTML** + - Interactive elements must have accessible names (aria-label, + aria-labelledby, or visible text content) + - Use semantic HTML elements where possible (`