diff --git a/dataset/codereview.jsonl b/dataset/codereview.jsonl
index 168c9d9ea..c8d67c400 100644
--- a/dataset/codereview.jsonl
+++ b/dataset/codereview.jsonl
@@ -1,81 +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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "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, "category": "code-review", "description": "True positive upgrade findings: other_upgrade (trimmed to reliably detected setup and InitValue upgrade risks)", "expect_findings": true, "source": "vsoadmin"}
+{"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."}], "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."}], "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()."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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."}], "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."}], "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."}], "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."}], "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."}], "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"}], "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."}], "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."}], "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."}], "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"}], "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."}], "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"}], "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."}], "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"}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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."}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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"}], "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."}], "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."}], "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."}], "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."}], "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"}], "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"}], "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"}], "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"}], "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"}], "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/dataset/judge_calibration.jsonl b/dataset/judge_calibration.jsonl
new file mode 100644
index 000000000..1c87c2386
--- /dev/null
+++ b/dataset/judge_calibration.jsonl
@@ -0,0 +1,24 @@
+{"expected": {"file": "src/Sales/SalesPost.Codeunit.al", "line_start": 142, "body": "Calling Commit() inside the repeat loop can leave partial data if a later iteration fails.", "severity": "medium"}, "candidate": {"file": "src/Sales/SalesPost.Codeunit.al", "line_start": 145, "body": "Move the Commit() out of the loop; committing per iteration breaks atomicity.", "severity": "medium"}, "should_match": true, "note": "commit-in-loop, paraphrased + different line"}
+{"expected": {"file": "src/Inventory/ItemAvail.Codeunit.al", "line_start": 58, "body": "Add SetLoadFields before FindSet so the whole record isn't loaded.", "severity": "medium"}, "candidate": {"file": "src/Inventory/ItemAvail.Codeunit.al", "line_start": 58, "body": "Use SetLoadFields to limit the columns read for this query.", "severity": "medium"}, "should_match": true, "note": "setloadfields performance, same issue"}
+{"expected": {"file": "src/Finance/Payment.Codeunit.al", "line_start": 77, "body": "Currency code 'USD' is hardcoded; read it from setup instead.", "severity": "medium"}, "candidate": {"file": "src/Finance/Payment.Codeunit.al", "line_start": 77, "body": "Don't hardcode the currency — pull it from the configuration record.", "severity": "medium"}, "should_match": true, "note": "hardcoded currency, same issue"}
+{"expected": {"file": "src/Sales/Customer.Codeunit.al", "line_start": 33, "body": "GET can fail when the customer is missing; handle the not-found case.", "severity": "medium"}, "candidate": {"file": "src/Sales/Customer.Codeunit.al", "line_start": 31, "body": "Missing error handling if the GET on Customer returns false.", "severity": "medium"}, "should_match": true, "note": "unchecked GET, same issue"}
+{"expected": {"file": "src/Sales/SalesLine.Table.al", "line_start": 210, "body": "You can't SetRange on a FlowField without calling CalcFields first.", "severity": "medium"}, "candidate": {"file": "src/Sales/SalesLine.Table.al", "line_start": 210, "body": "Filtering directly on this FlowField won't work as written.", "severity": "medium"}, "should_match": true, "note": "flowfield filter, same issue"}
+{"expected": {"file": "src/Reports/Statement.Report.al", "line_start": 90, "body": "Building the filter from a concatenated string risks filter injection; use SetFilter with parameters.", "severity": "medium"}, "candidate": {"file": "src/Reports/Statement.Report.al", "line_start": 92, "body": "Use parameterized SetFilter instead of concatenating user input into the filter.", "severity": "medium"}, "should_match": true, "note": "filter injection, same issue"}
+{"expected": {"file": "src/Inventory/Reorder.Codeunit.al", "line_start": 120, "body": "This runs a database read per record — an N+1 pattern.", "severity": "medium"}, "candidate": {"file": "src/Inventory/Reorder.Codeunit.al", "line_start": 118, "body": "Querying inside the loop causes N+1 queries; batch the lookup.", "severity": "medium"}, "should_match": true, "note": "n+1 query, same issue"}
+{"expected": {"file": "src/Sales/SalesPost.Codeunit.al", "line_start": 60, "body": "TestField(Quantity) is missing before posting.", "severity": "medium"}, "candidate": {"file": "src/Sales/SalesPost.Codeunit.al", "line_start": 60, "body": "Validate that Quantity is set before posting, e.g. with TestField.", "severity": "high"}, "should_match": true, "note": "missing testfield, severity differs"}
+{"expected": {"file": "src/Common/Util.Codeunit.al", "line_start": 12, "body": "Variable 'i' is declared but never used.", "severity": "low"}, "candidate": {"file": "src/Common/Util.Codeunit.al", "line_start": 12, "body": "Remove the unused variable i.", "severity": "high"}, "should_match": true, "note": "unused variable, severity differs"}
+{"expected": {"file": "src/Sales/SalesPost.Codeunit.al", "line_start": 142, "body": "Calling Commit() inside the repeat loop can leave partial data on failure.", "severity": "medium"}, "candidate": {"file": "src/Sales/SalesPost.Codeunit.al", "line_start": 142, "body": "The procedure name should be PascalCase to match conventions.", "severity": "medium"}, "should_match": false, "note": "commit-in-loop vs naming style, same line"}
+{"expected": {"file": "src/Admin/UserSetup.Page.al", "line_start": 45, "body": "Missing permission/SecurityFiltering check before exposing this data.", "severity": "medium"}, "candidate": {"file": "src/Admin/UserSetup.Page.al", "line_start": 45, "body": "Add a tooltip to this field for accessibility.", "severity": "medium"}, "should_match": false, "note": "permission vs tooltip, same line"}
+{"expected": {"file": "src/Common/Loop.Codeunit.al", "line_start": 88, "body": "Off-by-one: the range 1..Count skips the last element.", "severity": "medium"}, "candidate": {"file": "src/Common/Loop.Codeunit.al", "line_start": 88, "body": "Use a temporary record here to avoid locking the table.", "severity": "medium"}, "should_match": false, "note": "off-by-one vs locking, same line"}
+{"expected": {"file": "src/Sales/Customer.Codeunit.al", "line_start": 33, "body": "Possible null/empty reference: 'Customer' may be blank after the failed GET.", "severity": "medium"}, "candidate": {"file": "src/Sales/Customer.Codeunit.al", "line_start": 33, "body": "Add a comment explaining what this block does.", "severity": "medium"}, "should_match": false, "note": "null-ref vs missing comment, same line"}
+{"expected": {"file": "src/Inventory/ItemAvail.Codeunit.al", "line_start": 58, "body": "Use IsEmpty() instead of Count() = 0 for performance.", "severity": "medium"}, "candidate": {"file": "src/Inventory/ItemAvail.Codeunit.al", "line_start": 58, "body": "This label text needs to be translated/localized.", "severity": "medium"}, "should_match": false, "note": "isempty perf vs localization, same line"}
+{"expected": {"file": "src/Finance/Payment.Codeunit.al", "line_start": 77, "body": "Currency code 'USD' is hardcoded; read it from setup.", "severity": "medium"}, "candidate": {"file": "src/Finance/Payment.Codeunit.al", "line_start": 77, "body": "The magic number 1000 should be extracted into a named constant.", "severity": "medium"}, "should_match": false, "note": "hardcoded currency vs magic number — both 'hardcoding' but different code"}
+{"expected": {"file": "src/Inventory/Reorder.Codeunit.al", "line_start": 118, "body": "FindSet(true) should be FindSet(false) since records aren't modified.", "severity": "medium"}, "candidate": {"file": "src/Inventory/Reorder.Codeunit.al", "line_start": 118, "body": "Add SetLoadFields before this FindSet to limit columns.", "severity": "medium"}, "should_match": false, "note": "findset write-intent vs setloadfields — both about the same FindSet, different issue"}
+{"expected": {"file": "src/Sales/CustList.Codeunit.al", "line_start": 70, "body": "This loop reads every column of Customer; on large tables that's a major performance hit.", "severity": "medium"}, "candidate": {"file": "src/Sales/CustList.Codeunit.al", "line_start": 72, "body": "Call SetLoadFields(Name, \"No.\") before FindSet so only the needed fields are fetched.", "severity": "medium"}, "should_match": true, "note": "setloadfields: symptom (slow full read) vs fix (call SetLoadFields)"}
+{"expected": {"file": "src/Sales/SalesValidation.Codeunit.al", "line_start": 25, "body": "AA0217: the literal passed to Error() must be a Label variable.", "severity": "medium"}, "candidate": {"file": "src/Sales/SalesValidation.Codeunit.al", "line_start": 25, "body": "Hardcoding this error text breaks translation; move it to a Label with an Err suffix.", "severity": "medium"}, "should_match": true, "note": "hardcoded error string: rule number (AA0217) vs translation rationale"}
+{"expected": {"file": "src/Integration/Import.Codeunit.al", "line_start": 88, "body": "The InStream is read twice without resetting position; the second read returns nothing.", "severity": "medium"}, "candidate": {"file": "src/Integration/Import.Codeunit.al", "line_start": 90, "body": "Reset the stream position before reading it a second time.", "severity": "medium"}, "should_match": true, "note": "stream re-read: symptom (empty second read) vs fix (reset position)"}
+{"expected": {"file": "src/Admin/UserSetup.Page.al", "line_start": 12, "body": "This page exposes records without an OnOpenPage permission check.", "severity": "medium"}, "candidate": {"file": "src/Admin/UserSetup.Page.al", "line_start": 12, "body": "Add a SecurityFiltering/permission guard so unauthorized users can't read these records.", "severity": "medium"}, "should_match": true, "note": "missing permission check: terminology variance (OnOpenPage check vs SecurityFiltering guard)"}
+{"expected": {"file": "src/Sales/SalesPost.Codeunit.al", "line_start": 60, "body": "Missing TestField(Quantity) before posting.", "severity": "medium"}, "candidate": {"file": "src/Sales/SalesPost.Codeunit.al", "line_start": 60, "body": "Missing TestField(Type) before posting.", "severity": "medium"}, "should_match": false, "note": "missing TestField but different field (Quantity vs Type)"}
+{"expected": {"file": "src/Sales/SalesPost.Codeunit.al", "line_start": 142, "body": "Commit() inside the loop breaks atomicity — move it out.", "severity": "medium"}, "candidate": {"file": "src/Sales/SalesPost.Codeunit.al", "line_start": 142, "body": "There is no Commit() here, so the batch changes are never persisted.", "severity": "medium"}, "should_match": false, "note": "commit: too-many (in loop) vs missing entirely, opposite issue same line"}
+{"expected": {"file": "src/Inventory/Reorder.Codeunit.al", "line_start": 120, "body": "GET inside the loop is an N+1 performance problem.", "severity": "medium"}, "candidate": {"file": "src/Inventory/Reorder.Codeunit.al", "line_start": 120, "body": "The return value of this GET isn't checked; it can silently fail.", "severity": "medium"}, "should_match": false, "note": "same GET statement: N+1 performance vs unchecked-return correctness"}
+{"expected": {"file": "src/Sales/Notify.Codeunit.al", "line_start": 14, "body": "Hardcoded message string should be a Label (AA0217).", "severity": "medium"}, "candidate": {"file": "src/Sales/Notify.Codeunit.al", "line_start": 14, "body": "This Label uses a 'Msg' suffix but is passed to Error(); the suffix should be 'Err'.", "severity": "medium"}, "should_match": false, "note": "label: not-a-Label-at-all (AA0217) vs existing-Label-wrong-suffix"}
diff --git a/src/bcbench/agent/shared/config.yaml b/src/bcbench/agent/shared/config.yaml
index 30628c450..b5dd27f64 100644
--- a/src/bcbench/agent/shared/config.yaml
+++ b/src/bcbench/agent/shared/config.yaml
@@ -54,7 +54,17 @@ prompt:
/review
Review only the uncommitted working-tree changes (git diff HEAD); do not compare commits such as HEAD~1..HEAD or origin/main.
- Save the review findings to a file named "review.json" in the repository root (write an empty array if there are no findings).
+
+ Your only deliverable is a file named review.json in the repository root. You MUST write it before finishing; if you do not, your review is lost and counts as no output.
+
+ review.json must contain a single JSON array. Each finding is an object with these fields:
+ - file: repo-relative path of the file the finding refers to (string, required)
+ - line_start: 1-based line number where the issue starts (integer, required)
+ - line_end: line number where the issue ends (integer, optional)
+ - severity: one of critical, high, medium, or low (optional, defaults to medium)
+ - body: concise description of the issue (string, required)
+
+ If there are no findings, write an empty array. Write only valid JSON to review.json, with no surrounding markdown or commentary.
# controls:
# 1. whether to copy custom instructions from `src/bcbench/agent/shared/instructions//`
diff --git a/src/bcbench/commands/dataset.py b/src/bcbench/commands/dataset.py
index 91232dbcc..992bf236e 100644
--- a/src/bcbench/commands/dataset.py
+++ b/src/bcbench/commands/dataset.py
@@ -150,7 +150,7 @@ def view_entry(
lines = str(comment.line_start)
if comment.line_end and comment.line_end != comment.line_start:
lines += f"-{comment.line_end}"
- comment_table.add_row(comment.file, lines, comment.severity, comment.body)
+ comment_table.add_row(comment.file, lines, comment.severity_label, comment.body)
console.print(comment_table)
else:
console.print("[dim]No expected comments[/dim]")
diff --git a/src/bcbench/commands/evaluate.py b/src/bcbench/commands/evaluate.py
index bba37e9ca..43a43da6f 100644
--- a/src/bcbench/commands/evaluate.py
+++ b/src/bcbench/commands/evaluate.py
@@ -22,6 +22,7 @@
from bcbench.config import get_config
from bcbench.dataset import BaseDatasetEntry, NL2ALEntry
from bcbench.evaluate import EvaluationPipeline
+from bcbench.evaluate.codereview_judge_calibration import run_calibration
from bcbench.logger import get_logger
from bcbench.results import BaseEvaluationResult, CodeReviewResult, ExecutionBasedEvaluationResult, JudgeBasedEvaluationResult
from bcbench.types import AgentMetrics, BCalLLMBackend, ContainerConfig, EvaluationCategory, EvaluationContext, ExperimentConfiguration
@@ -203,6 +204,31 @@ def evaluate_bcal(
logger.info(f"Results saved to: {run_dir}")
+@evaluate_app.command("judge-calibration")
+def evaluate_judge_calibration(
+ model: CopilotModel = _config.judge.code_review_model,
+ work_dir: RepoPath = _config.paths.testbed_path,
+ min_accuracy: Annotated[float, typer.Option(help="Fail if judge accuracy falls below this")] = 0.8,
+) -> None:
+ """Run the LLM judge over the hand-labeled calibration set and report its precision/recall.
+
+ Intended for local/ad-hoc checks of the judge; exits non-zero if accuracy drops below
+ the threshold.
+ """
+ work_dir.mkdir(parents=True, exist_ok=True)
+ report = run_calibration(work_dir, model=model)
+
+ logger.info(f"Judge calibration ({model}) over {report.total} labeled pairs:")
+ logger.info(f" precision={report.precision:.3f} recall={report.recall:.3f} accuracy={report.accuracy:.3f}")
+ logger.info(f" TP={report.true_positives} FP={report.false_positives} TN={report.true_negatives} FN={report.false_negatives}")
+ for note in report.misclassified_notes:
+ logger.warning(f" {note}")
+
+ if report.accuracy < min_accuracy:
+ logger.error(f"Judge accuracy {report.accuracy:.3f} is below the required {min_accuracy:.3f}")
+ raise typer.Exit(code=1)
+
+
@evaluate_app.command("mock", hidden=True)
def evaluate_mock(
entry_id: Annotated[str, typer.Argument(help="Entry ID to run")],
@@ -302,7 +328,7 @@ def evaluate(self, context: EvaluationContext[BaseDatasetEntry]) -> None:
case "invalid":
result = CodeReviewResult.create_invalid(context, output="MOCK_INVALID_REVIEW_OUTPUT", expected_comments=[])
case "valid":
- result = CodeReviewResult.create(context, output="[]", expected_comments=[], generated_comments=[], line_tolerance=0)
+ result = CodeReviewResult.create(context, output="[]", expected_comments=[], generated_comments=[])
case "raw":
result = JudgeBasedEvaluationResult.create_raw(context, output="MOCK_PATCH_CONTENT")
case "empty":
diff --git a/src/bcbench/config.py b/src/bcbench/config.py
index 4440bd15d..576e484e2 100644
--- a/src/bcbench/config.py
+++ b/src/bcbench/config.py
@@ -9,6 +9,8 @@
from dotenv import load_dotenv
+from bcbench.cli_options import CopilotModel
+
__all__ = ["Config", "get_config"]
@@ -123,6 +125,21 @@ def default(cls) -> FilePatternConfig:
)
+@dataclass(frozen=True)
+class JudgeConfig:
+ """Configuration for the code-review LLM semantic judge."""
+
+ code_review_model: CopilotModel
+ result_file: str
+
+ @classmethod
+ def default(cls) -> JudgeConfig:
+ return cls(
+ code_review_model="gpt-5.3-codex",
+ result_file="judge_results.json",
+ )
+
+
@dataclass(frozen=True)
class EnvironmentConfig:
"""Environment-specific configuration."""
@@ -152,6 +169,7 @@ class Config:
env: EnvironmentConfig
timeout: TimeoutConfig
file_patterns: FilePatternConfig
+ judge: JudgeConfig
@classmethod
def load(cls) -> Config:
@@ -163,6 +181,7 @@ def load(cls) -> Config:
env=EnvironmentConfig.from_environment(),
timeout=TimeoutConfig.default(),
file_patterns=FilePatternConfig.default(),
+ judge=JudgeConfig.default(),
)
diff --git a/src/bcbench/dataset/codereview.py b/src/bcbench/dataset/codereview.py
index 3d5282a5b..fa8fe026f 100644
--- a/src/bcbench/dataset/codereview.py
+++ b/src/bcbench/dataset/codereview.py
@@ -20,10 +20,12 @@ def level(self) -> int:
@classmethod
def from_input(cls, value: str) -> Severity:
normalized = value.strip().lower()
- try:
+ if normalized in {s.value for s in cls}:
return cls(normalized)
- except ValueError:
- return _SEVERITY_ALIASES.get(normalized, cls.MEDIUM)
+ if normalized in _SEVERITY_ALIASES:
+ return _SEVERITY_ALIASES[normalized]
+ valid = [s.value for s in cls] + list(_SEVERITY_ALIASES)
+ raise ValueError(f"Unknown severity {value!r}; expected one of {valid}")
_SEVERITY_LEVELS: dict[Severity, int] = {
@@ -49,27 +51,30 @@ class ReviewComment(BaseModel):
line_end: int | None = None
domain: str | None = None
body: str
- severity: Severity
+ severity: Severity | None = None
@field_validator("severity", mode="before")
@classmethod
- def _coerce_severity(cls, value: object) -> Severity:
- if isinstance(value, Severity):
+ def _coerce_severity(cls, value: object) -> Severity | None:
+ if value is None or isinstance(value, Severity):
return value
return Severity.from_input(str(value))
+ @property
+ def severity_label(self) -> str:
+ return self.severity.value if self.severity is not None else "unspecified"
+
def __str__(self) -> str:
loc = f"{self.file}:{self.line_start}"
if self.line_end and self.line_end != self.line_start:
loc += f"-{self.line_end}"
- return f"[{self.severity}] {loc}: {self.body}"
+ return f"[{self.severity_label}] {loc}: {self.body}"
class CodeReviewEntry(BaseDatasetEntry):
"""Dataset entry for the code-review category."""
expected_comments: list[ReviewComment] = Field(default_factory=list)
- match_line_tolerance: int = Field(default=5, ge=0)
def get_task(self) -> str:
return self.patch
diff --git a/src/bcbench/evaluate/codereview.py b/src/bcbench/evaluate/codereview.py
index 8b90b0a24..73e010a2a 100644
--- a/src/bcbench/evaluate/codereview.py
+++ b/src/bcbench/evaluate/codereview.py
@@ -69,7 +69,6 @@ def evaluate(self, context: EvaluationContext[CodeReviewEntry]) -> None:
structural_matches = match_comments(
context.entry.expected_comments,
generated_comments,
- context.entry.match_line_tolerance,
)
validated_matches = judge_comment_matches(
structural_matches,
@@ -80,7 +79,6 @@ def evaluate(self, context: EvaluationContext[CodeReviewEntry]) -> None:
output=output,
expected_comments=context.entry.expected_comments,
generated_comments=generated_comments,
- line_tolerance=context.entry.match_line_tolerance,
matched_pairs=validated_matches,
)
logger.info(f"Parsed {len(result.generated_comments)} comments from {REVIEW_OUTPUT_FILE}")
diff --git a/src/bcbench/evaluate/codereview_judge.py b/src/bcbench/evaluate/codereview_judge.py
index a7bb26307..82d8f4825 100644
--- a/src/bcbench/evaluate/codereview_judge.py
+++ b/src/bcbench/evaluate/codereview_judge.py
@@ -18,10 +18,6 @@
_config = get_config()
-JUDGE_RESULT_FILE = "judge_results.json"
-
-JUDGE_MODEL = "gpt-5.3-codex"
-
_JUDGE_PROMPT_TEMPLATE = """
You are a code review evaluation judge. Your task is to determine whether pairs of code review comments identify the SAME underlying issue.
@@ -39,8 +35,8 @@
def _format_pair(index: int, expected: ReviewComment, generated: ReviewComment) -> str:
return (
f"Pair {index}:\n"
- f" Expected: [{expected.severity}] {expected.file}:{expected.line_start}: {expected.body}\n"
- f" Candidate: [{generated.severity}] {generated.file}:{generated.line_start}: {generated.body}"
+ f" Expected: [{expected.severity_label}] {expected.file}:{expected.line_start}: {expected.body}\n"
+ f" Candidate: [{generated.severity_label}] {generated.file}:{generated.line_start}: {generated.body}"
)
@@ -89,14 +85,31 @@ def _find_copilot() -> str | None:
return shutil.which("copilot.exe") or shutil.which("copilot.cmd") or shutil.which("copilot")
+def _decode_stream(stream: str | bytes | None) -> str:
+ if stream is None:
+ return ""
+ if isinstance(stream, bytes):
+ return stream.decode("utf-8", errors="replace")
+ return stream
+
+
+def _format_subprocess_output(exc: Exception, limit: int = 2000) -> str:
+ parts: list[str] = []
+ for label in ("stdout", "stderr"):
+ text = _decode_stream(getattr(exc, label, None)).strip()
+ if text:
+ parts.append(f"\n--- {label} ---\n{text[-limit:]}")
+ return "".join(parts)
+
+
def judge_comment_matches(
matched_pairs: list[tuple[ReviewComment, ReviewComment]],
work_dir: Path,
- model: str = JUDGE_MODEL,
+ model: str = _config.judge.code_review_model,
) -> list[tuple[ReviewComment, ReviewComment]]:
"""Validate structurally matched comment pairs using an LLM semantic judge.
- Defaults to a fixed judge model (``JUDGE_MODEL``) independent of the experiment
+ Defaults to a fixed judge model (``_config.judge.code_review_model``) independent of the experiment
model, so scores reflect AL review quality rather than a model judging itself.
Args:
@@ -114,12 +127,29 @@ def judge_comment_matches(
if not matched_pairs:
return []
+ verdicts = judge_verdicts(matched_pairs, work_dir, model=model)
+ return [pair for pair, is_match in zip(matched_pairs, verdicts, strict=True) if is_match]
+
+
+def judge_verdicts(
+ pairs: list[tuple[ReviewComment, ReviewComment]],
+ work_dir: Path,
+ model: str = _config.judge.code_review_model,
+) -> list[bool]:
+ """Run the semantic judge over comment pairs and return one match verdict per pair.
+
+ Raises:
+ LLMJudgeError: If the judge cannot run or produce a usable verdict.
+ """
+ if not pairs:
+ return []
+
copilot_cmd = _find_copilot()
if not copilot_cmd:
raise LLMJudgeError("Copilot CLI not found; cannot run the semantic judge")
- result_path = work_dir / JUDGE_RESULT_FILE
- prompt = " ".join(_build_judge_prompt(matched_pairs, JUDGE_RESULT_FILE).split())
+ result_path = work_dir / _config.judge.result_file
+ prompt = " ".join(_build_judge_prompt(pairs, _config.judge.result_file).split())
try:
completed = subprocess.run(
@@ -134,11 +164,12 @@ def judge_comment_matches(
cwd=str(work_dir),
capture_output=True,
text=True,
+ encoding="utf-8",
+ errors="replace",
timeout=_config.timeout.agent_execution,
check=True,
)
except (subprocess.TimeoutExpired, subprocess.CalledProcessError, OSError) as exc:
- raise LLMJudgeError(f"Judge subprocess failed: {exc}") from exc
+ raise LLMJudgeError(f"Judge subprocess failed: {exc}{_format_subprocess_output(exc)}") from exc
- verdicts = _parse_judge_results(result_path, len(matched_pairs), stdout=completed.stdout or "")
- return [pair for pair, is_match in zip(matched_pairs, verdicts, strict=True) if is_match]
+ return _parse_judge_results(result_path, len(pairs), stdout=completed.stdout or "")
diff --git a/src/bcbench/evaluate/codereview_judge_calibration.py b/src/bcbench/evaluate/codereview_judge_calibration.py
new file mode 100644
index 000000000..dbfbf13de
--- /dev/null
+++ b/src/bcbench/evaluate/codereview_judge_calibration.py
@@ -0,0 +1,85 @@
+"""Calibration set for the code-review LLM judge.
+
+A small, hand-labeled set of (expected, candidate) comment pairs with a human verdict on
+whether they describe the same underlying issue. Running the judge over this set yields its
+precision/recall against human judgement, so judge drift is caught loudly instead of silently
+distorting every code-review score.
+
+The non-match cases deliberately share the same file and line as their pair so the judge cannot
+pass on location alone — it must discriminate on the issue itself. The match cases deliberately
+differ in wording, severity, or line so semantic equivalence is exercised.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+from pydantic import BaseModel, ConfigDict
+
+from bcbench.config import get_config
+from bcbench.dataset.codereview import ReviewComment
+from bcbench.evaluate.codereview_judge import judge_verdicts
+from bcbench.results.metrics import precision_recall
+from bcbench.types import JudgeCalibrationReport
+
+_config = get_config()
+
+CALIBRATION_DATASET = _config.paths.dataset_dir / "judge_calibration.jsonl"
+
+
+class JudgeCalibrationCase(BaseModel):
+ model_config = ConfigDict(frozen=True)
+
+ expected: ReviewComment
+ candidate: ReviewComment
+ should_match: bool
+ note: str
+
+
+def _load_calibration_cases(path: Path = CALIBRATION_DATASET) -> list[JudgeCalibrationCase]:
+ return [JudgeCalibrationCase.model_validate_json(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()]
+
+
+def score_calibration(predicted: list[bool], cases: list[JudgeCalibrationCase]) -> JudgeCalibrationReport:
+ if len(predicted) != len(cases):
+ raise ValueError(f"Expected {len(cases)} verdicts, got {len(predicted)}")
+
+ tp = fp = tn = fn = 0
+ misclassified: list[str] = []
+ for verdict, case in zip(predicted, cases, strict=True):
+ if verdict and case.should_match:
+ tp += 1
+ elif verdict and not case.should_match:
+ fp += 1
+ misclassified.append(f"FALSE POSITIVE: {case.note}")
+ elif not verdict and case.should_match:
+ fn += 1
+ misclassified.append(f"FALSE NEGATIVE: {case.note}")
+ else:
+ tn += 1
+
+ precision, recall = precision_recall(tp, tp + fp, tp + fn)
+ accuracy = (tp + tn) / len(cases) if cases else 0.0
+
+ return JudgeCalibrationReport(
+ total=len(cases),
+ true_positives=tp,
+ false_positives=fp,
+ true_negatives=tn,
+ false_negatives=fn,
+ precision=precision,
+ recall=recall,
+ accuracy=accuracy,
+ misclassified_notes=misclassified,
+ )
+
+
+def run_calibration(work_dir: Path, model: str = _config.judge.code_review_model, dataset: Path = CALIBRATION_DATASET) -> JudgeCalibrationReport:
+ """Run the live judge over the calibration set and score it against the human labels.
+
+ Requires the Copilot CLI (raises LLMJudgeError otherwise).
+ """
+ cases = _load_calibration_cases(dataset)
+ pairs = [(case.expected, case.candidate) for case in cases]
+ verdicts = judge_verdicts(pairs, work_dir, model=model)
+ return score_calibration(verdicts, cases)
diff --git a/src/bcbench/evaluate/review_parsing.py b/src/bcbench/evaluate/review_parsing.py
index 4dea995ae..2a8f15834 100644
--- a/src/bcbench/evaluate/review_parsing.py
+++ b/src/bcbench/evaluate/review_parsing.py
@@ -47,7 +47,6 @@ def _normalize_comment(item: dict[Any, Any]) -> ReviewComment | None:
line_end = _to_int(item.get("line_end") or item.get("lineEnd") or item.get("endLine"))
domain = item.get("domain")
body = item.get("body") or item.get("issue") or item.get("comment")
- severity = Severity.from_input(str(item.get("severity", "medium")))
if not isinstance(file_path, str) or not file_path.strip():
return None
@@ -63,12 +62,24 @@ def _normalize_comment(item: dict[Any, Any]) -> ReviewComment | None:
line_end=line_end,
domain=domain.strip() if isinstance(domain, str) and domain.strip() else None,
body=body.strip(),
- severity=severity,
+ severity=_coerce_optional_severity(item.get("severity")),
)
except Exception:
return None
+def _coerce_optional_severity(value: object) -> Severity | None:
+ # Missing or unrecognized severities are kept as None (no default) so the finding still counts;
+ # this mirrors production review rendering, which surfaces unknown-severity findings rather than
+ # silently dropping or defaulting them.
+ if value is None:
+ return None
+ try:
+ return Severity.from_input(str(value))
+ except ValueError:
+ return None
+
+
def parse_review_output(raw_output: str) -> list[ReviewComment] | None:
"""Parse raw agent output into review comments.
diff --git a/src/bcbench/results/codereview.py b/src/bcbench/results/codereview.py
index b319d4e59..7305d2e59 100644
--- a/src/bcbench/results/codereview.py
+++ b/src/bcbench/results/codereview.py
@@ -19,7 +19,7 @@
📖 How to read these metrics
- **Micro** — sums matched/generated/expected across all tasks and computes one score; tasks with many comments dominate.
-- **Macro** — computes P/R/F1 per task and averages the scores; every task counts equally regardless of comment volume.
+- **Macro** — computes P/R/F1 per task and averages the scores; every task counts equally regardless of comment volume. Macro precision averages only over tasks where the agent left at least one comment, so staying silent is not scored as perfect precision (recall and F1 still span every task).
- **Matched comment** — a generated comment paired with an expected one by file and line proximity (within the configured tolerance), then confirmed by an LLM judge to describe the same underlying issue.
- **F1** — harmonic mean of precision and recall; balances both equally. (Special case of Fβ at β=1.)
- **Fβ** — generalized F-score with a tunable precision/recall trade-off:
@@ -40,7 +40,7 @@
_CONSOLE_METRIC_EXPLANATIONS = (
"[bold]Micro[/bold] — volume-weighted across all comments; tasks with many comments dominate.\n"
- "[bold]Macro[/bold] — per-task P/R/F1 averaged equally; every task counts the same.\n"
+ "[bold]Macro[/bold] — per-task P/R/F1 averaged equally; every task counts the same. Macro precision skips tasks with no comments so silence is not scored as perfect precision.\n"
"[bold]Matched comment[/bold] — paired by file + line proximity, then confirmed by an LLM judge to describe the same underlying issue.\n"
"[bold]F1[/bold] — harmonic mean of precision and recall (special case of Fβ at β=1).\n"
"[bold]Fβ[/bold] — F_β = (1 + β²) · (P · R) / (β² · P + R); β<1 favors precision, β>1 favors recall.\n"
@@ -75,7 +75,6 @@ def _line_distance(line: int, start: int, end: int | None) -> int:
def match_comments(
expected_comments: list[ReviewComment],
generated_comments: list[ReviewComment],
- line_tolerance: int,
) -> list[tuple[ReviewComment, ReviewComment]]:
"""Pair expected and generated comments by globally optimal (file, line-proximity) assignment.
@@ -83,14 +82,16 @@ def match_comments(
comment, maximizing the number of matches first and minimizing total line distance second.
A simple order-based greedy can let an earlier-listed expected comment steal a finding that is
a closer (often exact-line) match for a later expected comment, understating recall.
+
+ Only same-file findings are eligible; line distance never blocks a pair and acts solely as an
+ assignment tiebreak. The LLM judge is the authoritative semantic gate applied to these pairs.
"""
if not expected_comments or not generated_comments:
return []
num_expected = len(expected_comments)
num_generated = len(generated_comments)
- impossible = float(line_tolerance * min(num_expected, num_generated) + 1)
- cost = np.full((num_expected, num_generated), impossible, dtype=float)
+ cost = np.full((num_expected, num_generated), np.inf, dtype=float)
for expected_index, expected in enumerate(expected_comments):
expected_file = _normalize_path(expected.file)
@@ -98,26 +99,30 @@ def match_comments(
if _normalize_path(generated.file) != expected_file:
continue
distance = _line_distance(generated.line_start, expected.line_start, expected.line_end)
- if distance <= line_tolerance:
- cost[expected_index, generated_index] = distance
+ cost[expected_index, generated_index] = distance
+
+ finite = cost[np.isfinite(cost)]
+ sentinel = float(finite.max() + 1.0) if finite.size else 1.0
+ solvable = np.where(np.isfinite(cost), cost, sentinel)
- row_indices, column_indices = linear_sum_assignment(cost)
+ row_indices, column_indices = linear_sum_assignment(solvable)
return [
(expected_comments[expected_index], generated_comments[generated_index])
for expected_index, generated_index in zip(row_indices, column_indices, strict=False)
- if cost[expected_index, generated_index] <= line_tolerance
+ if np.isfinite(cost[expected_index, generated_index])
]
def _severity_mean_absolute_error(matched_pairs: list[tuple[ReviewComment, ReviewComment]]) -> float:
"""Mean absolute difference between expected and generated severity levels over matched pairs.
- Returns 0.0 when there are no matched pairs.
+ Pairs where either side has no severity are skipped, since prod keeps unknown-severity findings
+ rather than defaulting them. Returns 0.0 when there are no scorable pairs.
"""
- if not matched_pairs:
+ errors = [abs(expected.severity.level - generated.severity.level) for expected, generated in matched_pairs if expected.severity is not None and generated.severity is not None]
+ if not errors:
return 0.0
- total_error: int = sum(abs(expected.severity.level - generated.severity.level) for expected, generated in matched_pairs)
- return total_error / len(matched_pairs)
+ return sum(errors) / len(errors)
class CodeReviewResult(BaseEvaluationResult):
@@ -125,7 +130,6 @@ class CodeReviewResult(BaseEvaluationResult):
generated_comments: list[ReviewComment] = Field(default_factory=list)
expected_comments: list[ReviewComment] = Field(default_factory=list)
- line_tolerance: int = Field(ge=0)
valid_review_output: bool = False
matched_comment_count: int = Field(default=0, ge=0)
@@ -146,11 +150,10 @@ def create(
output: str,
expected_comments: list[ReviewComment],
generated_comments: list[ReviewComment],
- line_tolerance: int,
matched_pairs: list[tuple[ReviewComment, ReviewComment]] | None = None,
) -> Self:
if matched_pairs is None:
- matched_pairs = match_comments(expected_comments, generated_comments, line_tolerance)
+ matched_pairs = match_comments(expected_comments, generated_comments)
matched_count = len(matched_pairs)
precision, recall = precision_recall(matched_count, len(generated_comments), len(expected_comments))
@@ -159,7 +162,6 @@ def create(
output=output,
expected_comments=expected_comments,
generated_comments=generated_comments,
- line_tolerance=line_tolerance,
valid_review_output=True,
matched_comment_count=matched_count,
incorrect_comment_count=len(generated_comments) - matched_count,
@@ -184,7 +186,6 @@ def create_invalid(
**cls._base_fields(context),
output=output,
expected_comments=expected_comments,
- line_tolerance=0,
valid_review_output=False,
)
@@ -246,6 +247,10 @@ class CodeReviewResultSummary(EvaluationResultSummary):
severity_mae: float = 0.0
valid_review_output_rate: float = Field(default=0.0, ge=0.0, le=1.0)
+ # Per-task F1 scores, retained so the leaderboard can bootstrap a confidence interval over tasks
+ # (meaningful even for a single run) instead of only over runs.
+ per_task_f1: list[float] = Field(default_factory=list)
+
def render_github_metrics_markdown(self) -> str:
micro_p = self.precision * 100
micro_r = self.recall * 100
@@ -356,7 +361,11 @@ def from_results(cls, results: Sequence[BaseEvaluationResult], run_id: str) -> "
f_beta_05: float = f_beta_score(precision, recall, beta=0.5)
f_beta_2: float = f_beta_score(precision, recall, beta=2.0)
- macro_precision: float = sum(r.precision for r in code_review_results) / total_results
+ # Precision is only defined where the agent actually commented. Silent tasks are assigned
+ # precision=1.0 by convention; averaging those in would reward saying nothing, so they are
+ # excluded from macro precision. Recall/F1 still span all tasks, so silence is penalized there.
+ results_with_comments = [r for r in code_review_results if r.generated_comments]
+ macro_precision: float = sum(r.precision for r in results_with_comments) / len(results_with_comments) if results_with_comments else 0.0
macro_recall: float = sum(r.recall for r in code_review_results) / total_results
macro_f1: float = sum(r.f1 for r in code_review_results) / total_results
macro_f_beta_05: float = sum(r.f_beta_05 for r in code_review_results) / total_results
@@ -388,5 +397,6 @@ def from_results(cls, results: Sequence[BaseEvaluationResult], run_id: str) -> "
"macro_f_beta_2": round(macro_f_beta_2, 3),
"severity_mae": round(severity_mae, 3),
"valid_review_output_rate": round(valid_output_rate, 3),
+ "per_task_f1": [round(r.f1, 6) for r in code_review_results],
}
)
diff --git a/src/bcbench/results/leaderboard.py b/src/bcbench/results/leaderboard.py
index a2c4424e7..7a0d3df05 100644
--- a/src/bcbench/results/leaderboard.py
+++ b/src/bcbench/results/leaderboard.py
@@ -143,8 +143,16 @@ def from_runs(cls, runs: Sequence[EvaluationResultSummary]) -> "CodeReviewLeader
cr_runs: list[CodeReviewResultSummary] = [run for run in runs if isinstance(run, CodeReviewResultSummary)]
n = len(cr_runs)
+ # The micro headline pools every comment across the dataset, so there is no per-task
+ # decomposition to resample; its CI is intentionally over run-level means and captures
+ # run-to-run reproducibility (None unless >=2 runs with variance).
f1_ci = bootstrap_ci([r.f1 for r in cr_runs])
- macro_f1_ci = bootstrap_ci([r.macro_f1 for r in cr_runs])
+ # The macro headline weights tasks equally, so we bootstrap the equal-weight headline over the
+ # pooled per-task F1 scores across runs: the CI reflects task-level variance (resampling tasks),
+ # which is the dominant sampling uncertainty for our small task set and is meaningful even for a
+ # single run. This deliberately differs from the per-run micro CI above.
+ pooled_task_f1 = [score for r in cr_runs for score in r.per_task_f1]
+ macro_f1_ci = bootstrap_ci(pooled_task_f1)
return base.model_copy(
update={
diff --git a/src/bcbench/types.py b/src/bcbench/types.py
index 334083f30..b70821d05 100644
--- a/src/bcbench/types.py
+++ b/src/bcbench/types.py
@@ -28,6 +28,7 @@
"EvaluationContext",
"ExpectedOutput",
"ExperimentConfiguration",
+ "JudgeCalibrationReport",
]
@@ -303,6 +304,19 @@ class ContainerConfig:
password: str
+@dataclass(frozen=True)
+class JudgeCalibrationReport:
+ total: int
+ true_positives: int
+ false_positives: int
+ true_negatives: int
+ false_negatives: int
+ precision: float
+ recall: float
+ accuracy: float # (TP + TN) / total: share of judge verdicts that match the human label, not F1
+ misclassified_notes: list[str]
+
+
@dataclass
class EvaluationContext[E: BaseDatasetEntry]:
"""Context object containing all configuration for evaluation pipeline.
diff --git a/tests/conftest.py b/tests/conftest.py
index c48d910e1..789dca6a2 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -191,7 +191,6 @@ def create_codereview_result(
agent_name: str = "copilot-cli",
output: str = '[{"file": "test.al", "line_start": 5, "body": "Good catch"}]',
expected_comments: list[ReviewComment] | None = None,
- line_tolerance: int = 5,
metrics: AgentMetrics | None = None,
domain: str | None = None,
) -> CodeReviewResult:
@@ -217,7 +216,6 @@ def create_codereview_result(
output=output,
expected_comments=expected_comments,
generated_comments=generated_comments,
- line_tolerance=line_tolerance,
)
diff --git a/tests/test_codereview.py b/tests/test_codereview.py
index 1784a19b8..8681f8b93 100644
--- a/tests/test_codereview.py
+++ b/tests/test_codereview.py
@@ -5,15 +5,19 @@
import pytest
+from bcbench.config import get_config
from bcbench.dataset import CodeReviewEntry
from bcbench.dataset.codereview import ReviewComment, Severity
from bcbench.evaluate.codereview import CodeReviewPipeline
-from bcbench.evaluate.codereview_judge import JUDGE_RESULT_FILE, LLMJudgeError, _parse_judge_results, judge_comment_matches
+from bcbench.evaluate.codereview_judge import LLMJudgeError, _parse_judge_results, judge_comment_matches
+from bcbench.evaluate.review_parsing import parse_review_output
from bcbench.results.base import BaseEvaluationResult
from bcbench.results.codereview import CodeReviewResult, CodeReviewResultSummary, match_comments
from bcbench.types import EvaluationCategory
from tests.conftest import create_codereview_entry, create_codereview_result, create_evaluation_context
+_config = get_config()
+
class TestSeverity:
def test_canonical_values_parse_directly(self):
@@ -26,8 +30,9 @@ def test_aliases_map_to_canonical_severities(self):
assert Severity.from_input("suggestion") is Severity.LOW
assert Severity.from_input("info") is Severity.LOW
- def test_unknown_severity_defaults_to_medium(self):
- assert Severity.from_input("bogus") is Severity.MEDIUM
+ def test_unknown_severity_raises(self):
+ with pytest.raises(ValueError, match="Unknown severity"):
+ Severity.from_input("bogus")
def test_levels_are_strictly_ordered(self):
assert Severity.CRITICAL.level > Severity.HIGH.level > Severity.MEDIUM.level > Severity.LOW.level
@@ -36,6 +41,28 @@ def test_review_comment_normalizes_severity_on_construction(self):
comment = ReviewComment.model_validate({"file": "src/app.al", "line_start": 1, "body": "x", "severity": "warning"})
assert comment.severity is Severity.MEDIUM
+ def test_parser_keeps_comment_with_unknown_severity_as_unspecified(self):
+ output = json.dumps(
+ [
+ {"file": "a.al", "line_start": 1, "body": "valid", "severity": "high"},
+ {"file": "a.al", "line_start": 2, "body": "bad severity", "severity": "bogus"},
+ {"file": "a.al", "line_start": 3, "body": "missing severity"},
+ ]
+ )
+ comments = parse_review_output(output)
+ assert comments is not None
+ assert [(c.body, c.severity) for c in comments] == [
+ ("valid", Severity.HIGH),
+ ("bad severity", None),
+ ("missing severity", None),
+ ]
+
+ def test_unspecified_severity_renders_as_label(self):
+ comment = ReviewComment(file="a.al", line_start=1, body="x")
+ assert comment.severity is None
+ assert comment.severity_label == "unspecified"
+ assert str(comment) == "[unspecified] a.al:1: x"
+
class TestMatchComments:
def test_finding_matches_nearest_expected_not_first_listed(self):
@@ -45,14 +72,14 @@ def test_finding_matches_nearest_expected_not_first_listed(self):
]
generated = [ReviewComment(file="a.al", line_start=16, body="Commit() inside repeat", severity=Severity.HIGH)]
- pairs = match_comments(expected, generated, line_tolerance=2)
+ pairs = match_comments(expected, generated)
assert len(pairs) == 1
matched_expected, matched_generated = pairs[0]
assert matched_expected.line_start == 16
assert matched_generated.line_start == 16
- def test_maximizes_number_of_matches_within_tolerance(self):
+ def test_maximizes_number_of_matches(self):
expected = [
ReviewComment(file="a.al", line_start=10, body="issue A", severity=Severity.HIGH),
ReviewComment(file="a.al", line_start=11, body="issue B", severity=Severity.HIGH),
@@ -62,7 +89,7 @@ def test_maximizes_number_of_matches_within_tolerance(self):
ReviewComment(file="a.al", line_start=12, body="near A only", severity=Severity.HIGH),
]
- pairs = match_comments(expected, generated, line_tolerance=2)
+ pairs = match_comments(expected, generated)
assert len(pairs) == 2
assert {matched_expected.line_start for matched_expected, _ in pairs} == {10, 11}
@@ -71,18 +98,33 @@ def test_no_match_across_files(self):
expected = [ReviewComment(file="a.al", line_start=10, body="x", severity=Severity.HIGH)]
generated = [ReviewComment(file="b.al", line_start=10, body="x", severity=Severity.HIGH)]
- assert match_comments(expected, generated, line_tolerance=5) == []
+ assert match_comments(expected, generated) == []
- def test_no_match_beyond_tolerance(self):
+ def test_empty_inputs_return_no_pairs(self):
+ comment = ReviewComment(file="a.al", line_start=1, body="x", severity=Severity.HIGH)
+ assert match_comments([], [comment]) == []
+ assert match_comments([comment], []) == []
+
+ def test_pairs_same_file_regardless_of_distance(self):
expected = [ReviewComment(file="a.al", line_start=10, body="x", severity=Severity.HIGH)]
- generated = [ReviewComment(file="a.al", line_start=20, body="x", severity=Severity.HIGH)]
+ generated = [ReviewComment(file="a.al", line_start=900, body="x", severity=Severity.HIGH)]
- assert match_comments(expected, generated, line_tolerance=5) == []
+ pairs = match_comments(expected, generated)
- def test_empty_inputs_return_no_pairs(self):
- comment = ReviewComment(file="a.al", line_start=1, body="x", severity=Severity.HIGH)
- assert match_comments([], [comment], line_tolerance=5) == []
- assert match_comments([comment], [], line_tolerance=5) == []
+ assert len(pairs) == 1
+
+ def test_uses_distance_as_tiebreak(self):
+ expected = [ReviewComment(file="a.al", line_start=10, body="x", severity=Severity.HIGH)]
+ generated = [
+ ReviewComment(file="a.al", line_start=500, body="far", severity=Severity.HIGH),
+ ReviewComment(file="a.al", line_start=12, body="near", severity=Severity.HIGH),
+ ]
+
+ pairs = match_comments(expected, generated)
+
+ assert len(pairs) == 1
+ _, matched_generated = pairs[0]
+ assert matched_generated.line_start == 12
class TestCodeReviewEntry:
@@ -152,7 +194,6 @@ def test_category_loads_from_string(self):
"agent_name": "copilot-cli",
"category": "code-review",
"output": "",
- "line_tolerance": 5,
}
result = BaseEvaluationResult.from_json(payload)
@@ -224,7 +265,7 @@ def test_metrics_match_expected_comments_with_tolerance(self):
]
)
- result = create_codereview_result(output=generated_output, expected_comments=expected_comments, line_tolerance=5)
+ result = create_codereview_result(output=generated_output, expected_comments=expected_comments)
assert result.matched_comment_count == 1
assert result.missed_comment_count == 1
@@ -234,7 +275,25 @@ def test_metrics_match_expected_comments_with_tolerance(self):
assert result.f1 == 0.5
assert result.severity_mae == 0.0
- def test_severity_aliases_normalize_to_skill_levels(self):
+ def test_severity_mae_skips_pairs_with_unspecified_severity(self):
+ expected_comments = [
+ ReviewComment(file="src/app.al", line_start=10, body="A", severity=Severity.HIGH),
+ ReviewComment(file="src/app.al", line_start=40, body="B", severity=Severity.LOW),
+ ]
+ generated_output = json.dumps(
+ [
+ {"file": "src/app.al", "line_start": 11, "body": "near A", "severity": "bogus"},
+ {"file": "src/app.al", "line_start": 41, "body": "near B", "severity": "medium"},
+ ]
+ )
+
+ result = create_codereview_result(output=generated_output, expected_comments=expected_comments)
+
+ assert result.matched_comment_count == 2
+ assert result.generated_comments[0].severity is None
+ # Only the (LOW, MEDIUM) pair is scorable; the unspecified-severity pair is skipped.
+ assert result.severity_mae == 1.0
+
result = create_codereview_result(
output=json.dumps(
[
@@ -270,7 +329,7 @@ def test_display_row_splits_comment_counts(self):
]
)
- result = create_codereview_result(output=generated_output, expected_comments=expected_comments, line_tolerance=5)
+ result = create_codereview_result(output=generated_output, expected_comments=expected_comments)
assert result.display_row == {
"Generated": "2",
@@ -346,6 +405,36 @@ def test_summary_aggregates_precision_recall_and_f1(self):
assert summary.recall == 0.25
assert summary.f1 == 0.333
+ def test_macro_precision_excludes_silent_tasks(self):
+ expected_comments = [ReviewComment(file="src/app.al", line_start=10, body="Fix null check", severity=Severity.MEDIUM)]
+
+ commenting = create_codereview_result(
+ instance_id="test__macro-1",
+ output=json.dumps([{"file": "src/app.al", "line_start": 10, "body": "Issue A", "severity": "warning"}]),
+ expected_comments=expected_comments,
+ )
+ silent = create_codereview_result(
+ instance_id="test__macro-2",
+ output="[]",
+ expected_comments=expected_comments,
+ )
+
+ summary = CodeReviewResultSummary.from_results([commenting, silent], run_id="run-1")
+
+ # Only the task where the agent actually commented (precision 1.0) feeds macro precision;
+ # the silent task's convention precision of 1.0 must not be averaged in.
+ assert summary.macro_precision == 1.0
+ # Recall still spans both tasks: 1.0 for the hit, 0.0 for the silent miss.
+ assert summary.macro_recall == 0.5
+
+ def test_macro_precision_zero_when_all_tasks_silent(self):
+ expected_comments = [ReviewComment(file="src/app.al", line_start=10, body="Fix null check", severity=Severity.MEDIUM)]
+ silent = create_codereview_result(instance_id="test__silent-1", output="[]", expected_comments=expected_comments)
+
+ summary = CodeReviewResultSummary.from_results([silent], run_id="run-1")
+
+ assert summary.macro_precision == 0.0
+
def test_render_github_metrics_markdown_has_grouped_sections(self):
expected_comments = [
ReviewComment(file="src/app.al", line_start=10, body="Fix null check", severity=Severity.MEDIUM),
@@ -443,6 +532,31 @@ def test_aggregate_uses_f1_as_average_and_has_no_pass_hat_5(self):
assert agg.f1 == run.f1
assert not hasattr(agg, "pass_hat_5")
+ def test_macro_f1_ci_is_bootstrapped_over_tasks_for_single_run(self):
+ from bcbench.results.leaderboard import CodeReviewLeaderboardAggregate, LeaderboardAggregate
+
+ expected = [ReviewComment(file="src/app.al", line_start=10, body="Fix null check", severity=Severity.MEDIUM)]
+ hit = json.dumps([{"file": "src/app.al", "line_start": 10, "body": "Issue A", "severity": "warning"}])
+
+ results = [
+ create_codereview_result(instance_id="test__t-1", output=hit, expected_comments=expected),
+ create_codereview_result(instance_id="test__t-2", output="[]", expected_comments=expected),
+ create_codereview_result(instance_id="test__t-3", output=hit, expected_comments=expected),
+ create_codereview_result(instance_id="test__t-4", output="[]", expected_comments=expected),
+ ]
+ run = CodeReviewResultSummary.from_results(results, run_id="run-1")
+
+ # Per-task F1 is retained so the CI can be bootstrapped over tasks.
+ assert run.per_task_f1 == [1.0, 0.0, 1.0, 0.0]
+
+ agg = LeaderboardAggregate.from_runs([run])
+
+ # A single run with varying per-task F1 still yields a real (task-level) CI.
+ assert isinstance(agg, CodeReviewLeaderboardAggregate)
+ assert agg.macro_f1_ci_low is not None
+ assert agg.macro_f1_ci_high is not None
+ assert agg.macro_f1_ci_low <= agg.macro_f1 <= agg.macro_f1_ci_high
+
def test_aggregate_serialization_excludes_pass_hat_5(self):
from bcbench.results.leaderboard import Leaderboard, LeaderboardAggregate
@@ -563,30 +677,30 @@ def _pair(line: int) -> tuple[ReviewComment, ReviewComment]:
def test_parse_raises_when_result_file_missing(self, tmp_path):
with pytest.raises(LLMJudgeError, match="no result file"):
- _parse_judge_results(tmp_path / JUDGE_RESULT_FILE, num_pairs=1)
+ _parse_judge_results(tmp_path / _config.judge.result_file, num_pairs=1)
def test_parse_raises_on_invalid_json(self, tmp_path):
- result_path = tmp_path / JUDGE_RESULT_FILE
+ result_path = tmp_path / _config.judge.result_file
result_path.write_text("not json", encoding="utf-8")
with pytest.raises(LLMJudgeError, match="not valid JSON"):
_parse_judge_results(result_path, num_pairs=1)
def test_parse_raises_when_not_a_list(self, tmp_path):
- result_path = tmp_path / JUDGE_RESULT_FILE
+ result_path = tmp_path / _config.judge.result_file
result_path.write_text('{"pair": 1, "match": true}', encoding="utf-8")
with pytest.raises(LLMJudgeError, match="must be a JSON list"):
_parse_judge_results(result_path, num_pairs=1)
def test_parse_missing_pair_counts_as_not_confirmed(self, tmp_path):
- result_path = tmp_path / JUDGE_RESULT_FILE
+ result_path = tmp_path / _config.judge.result_file
result_path.write_text('[{"pair": 1, "match": true}]', encoding="utf-8")
assert _parse_judge_results(result_path, num_pairs=2) == [True, False]
def test_parse_falls_back_to_stdout_when_file_missing(self, tmp_path):
- result_path = tmp_path / JUDGE_RESULT_FILE
+ result_path = tmp_path / _config.judge.result_file
assert _parse_judge_results(result_path, num_pairs=1, stdout='```json\n[{"pair": 1, "match": true}]\n```') == [True]
@@ -608,11 +722,20 @@ def test_raises_when_subprocess_fails(self, tmp_path):
):
judge_comment_matches([self._pair(10)], work_dir=tmp_path)
+ def test_subprocess_failure_surfaces_copilot_output(self, tmp_path):
+ error = subprocess.CalledProcessError(1, "copilot", output="partial stdout", stderr="model gpt-5.3-codex is not available")
+ with (
+ patch("bcbench.evaluate.codereview_judge._find_copilot", return_value="copilot"),
+ patch("bcbench.evaluate.codereview_judge.subprocess.run", side_effect=error),
+ pytest.raises(LLMJudgeError, match="model gpt-5\\.3-codex is not available"),
+ ):
+ judge_comment_matches([self._pair(10)], work_dir=tmp_path)
+
def test_filters_to_confirmed_pairs(self, tmp_path):
pairs = [self._pair(10), self._pair(20)]
def fake_run(*args, **kwargs):
- (tmp_path / JUDGE_RESULT_FILE).write_text('[{"pair": 1, "match": true}, {"pair": 2, "match": false}]', encoding="utf-8")
+ (tmp_path / _config.judge.result_file).write_text('[{"pair": 1, "match": true}, {"pair": 2, "match": false}]', encoding="utf-8")
return subprocess.CompletedProcess(args, 0)
with (
diff --git a/tests/test_codereview_judge_calibration.py b/tests/test_codereview_judge_calibration.py
new file mode 100644
index 000000000..d7ee666dd
--- /dev/null
+++ b/tests/test_codereview_judge_calibration.py
@@ -0,0 +1,65 @@
+import os
+
+import pytest
+
+from bcbench.evaluate.codereview_judge import _find_copilot
+from bcbench.evaluate.codereview_judge_calibration import (
+ _load_calibration_cases,
+ run_calibration,
+ score_calibration,
+)
+
+CALIBRATION_CASES = _load_calibration_cases()
+
+
+class TestCalibrationDataset:
+ def test_has_both_match_and_non_match_cases(self):
+ matches = [c for c in CALIBRATION_CASES if c.should_match]
+ non_matches = [c for c in CALIBRATION_CASES if not c.should_match]
+ assert len(matches) >= 5
+ assert len(non_matches) >= 5
+
+ def test_notes_are_unique(self):
+ notes = [c.note for c in CALIBRATION_CASES]
+ assert len(notes) == len(set(notes))
+
+ def test_gold_labels_score_perfectly_against_themselves(self):
+ predicted = [c.should_match for c in CALIBRATION_CASES]
+ report = score_calibration(predicted, CALIBRATION_CASES)
+ assert report.precision == 1.0
+ assert report.recall == 1.0
+ assert report.accuracy == 1.0
+ assert report.misclassified_notes == []
+
+
+class TestScoreCalibration:
+ def test_length_mismatch_raises(self):
+ with pytest.raises(ValueError, match="verdicts"):
+ score_calibration([True], CALIBRATION_CASES)
+
+ def test_counts_confusion_matrix(self):
+ cases = CALIBRATION_CASES
+ # Predict everything as a match: every true match is a TP, every non-match is a FP.
+ report = score_calibration([True] * len(cases), cases)
+ expected_matches = sum(c.should_match for c in cases)
+ assert report.true_positives == expected_matches
+ assert report.false_positives == len(cases) - expected_matches
+ assert report.false_negatives == 0
+ assert report.recall == 1.0
+
+ def test_all_wrong_predictions(self):
+ cases = CALIBRATION_CASES
+ report = score_calibration([not c.should_match for c in cases], cases)
+ assert report.true_positives == 0
+ assert report.accuracy == 0.0
+ assert len(report.misclassified_notes) == len(cases)
+
+
+@pytest.mark.integration
+@pytest.mark.skipif(
+ not os.environ.get("BCBENCH_RUN_JUDGE_CALIBRATION") or _find_copilot() is None,
+ reason="Live judge calibration is opt-in (set BCBENCH_RUN_JUDGE_CALIBRATION and install the Copilot CLI)",
+)
+def test_live_judge_meets_accuracy_threshold(tmp_path):
+ report = run_calibration(tmp_path)
+ assert report.accuracy >= 0.8, f"Judge accuracy {report.accuracy:.3f} below threshold; misclassified: {report.misclassified_notes}"