mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-06 09:26:52 +01:00
Add lifecycle error and privacy knowledge (#99)
* Add lifecycle error and privacy knowledge Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 95c06ad8-377d-4faa-8d07-06300b1c81ec * Fix lifecycle privacy review findings Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a26cd6d6-ff49-433e-bc53-f645c455ebdd * Refine lifecycle privacy retrieval Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 95c06ad8-377d-4faa-8d07-06300b1c81ec * Make review gates explicit Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 95c06ad8-377d-4faa-8d07-06300b1c81ec --------- Co-authored-by: Jesper Schulz-Wedde <jesper.schulzwedde@microsoft.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
363f08f47e
commit
e0ebdd35c7
25 changed files with 459 additions and 39 deletions
|
|
@ -0,0 +1,12 @@
|
|||
codeunit 50308 "ErrorInfo Privacy Bad"
|
||||
{
|
||||
procedure RaiseSynchronizationError(Customer: Record Customer)
|
||||
var
|
||||
FailureInfo: ErrorInfo;
|
||||
begin
|
||||
FailureInfo.Message := StrSubstNo('Synchronization failed for %1.', Customer."E-Mail");
|
||||
FailureInfo.DataClassification := DataClassification::SystemMetadata;
|
||||
FailureInfo.ErrorType := ErrorType::Internal;
|
||||
Error(FailureInfo);
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
codeunit 50307 "ErrorInfo Privacy Good"
|
||||
{
|
||||
procedure RaiseSynchronizationError()
|
||||
var
|
||||
FailureInfo: ErrorInfo;
|
||||
begin
|
||||
FailureInfo.Message := SynchronizationFailedErr;
|
||||
FailureInfo.DataClassification := DataClassification::SystemMetadata;
|
||||
FailureInfo.ErrorType := ErrorType::Client;
|
||||
Error(FailureInfo);
|
||||
end;
|
||||
|
||||
var
|
||||
SynchronizationFailedErr: Label 'The synchronization could not be completed.';
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [14..]
|
||||
domain: privacy
|
||||
keywords: [errorinfo, errorinfo-message, errorinfo-dataclassification, errorinfo-errortype, errorinfo-detailedmessage, copy-details, telemetry]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Review each ErrorInfo text surface by its actual exposure
|
||||
|
||||
## Description
|
||||
|
||||
Runtime 3.0 (BC 14) provides `ErrorInfo.Message`, `DataClassification`, and `ErrorType`. `Message` is sent to telemetry; with `ErrorType::Client` it is also the primary client message, while `ErrorType::Internal` replaces it in the client with a generic message but still sends the specified text to telemetry. `DataClassification` classifies the content in `Message`; it does not make incorrectly classified personal data safe. Runtime 8.0 (BC 19) adds `DetailedMessage`, which is omitted from the primary message but included in the error dialog's **Copy details** content.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Keep `Message` stable and classify its actual content. Choose `ErrorType` for client usability, not as a telemetry privacy boundary. On BC 19 and later, put only support-safe technical context in `DetailedMessage`, because a user can copy it from the dialog. The samples use only members available at the BC 14 article floor.
|
||||
|
||||
See sample: `errorinfo-telemetry-classification-and-errortype.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Marking a dynamic customer-bearing `Message` as `SystemMetadata`, or assuming `ErrorType::Internal` keeps it out of telemetry. On BC 19 and later, the same anti-pattern includes placing secrets or personal data in `DetailedMessage` because it is not the primary dialog text.
|
||||
|
||||
See sample: `errorinfo-telemetry-classification-and-errortype.bad.al`.
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
codeunit 50310 "LogError Privacy Bad"
|
||||
{
|
||||
procedure SendInvoice()
|
||||
var
|
||||
FeatureTelemetry: Codeunit "Feature Telemetry";
|
||||
CustomDimensions: Dictionary of [Text, Text];
|
||||
ErrorCallStack: Text;
|
||||
ErrorText: Text;
|
||||
begin
|
||||
if TrySendInvoice() then
|
||||
exit;
|
||||
|
||||
ErrorText := GetLastErrorText();
|
||||
ErrorCallStack := GetLastErrorCallStack();
|
||||
CustomDimensions.Add('Operation', 'SendInvoice');
|
||||
FeatureTelemetry.LogError('0000FT2', 'Invoice exchange', 'Sending invoice',
|
||||
ErrorText, ErrorCallStack, CustomDimensions);
|
||||
end;
|
||||
|
||||
[TryFunction]
|
||||
local procedure TrySendInvoice()
|
||||
begin
|
||||
Error('Invoice %1 for %2 could not be sent.', 'INV-1001', 'user@example.com');
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
codeunit 50309 "LogError Privacy Good"
|
||||
{
|
||||
procedure SendInvoice()
|
||||
var
|
||||
FeatureTelemetry: Codeunit "Feature Telemetry";
|
||||
CustomDimensions: Dictionary of [Text, Text];
|
||||
ErrorCallStack: Text;
|
||||
ErrorText: Text;
|
||||
begin
|
||||
if TrySendInvoice() then
|
||||
exit;
|
||||
|
||||
ErrorText := GetLastErrorText(true);
|
||||
ErrorCallStack := GetLastErrorCallStack();
|
||||
CustomDimensions.Add('Operation', 'SendInvoice');
|
||||
FeatureTelemetry.LogError('0000FT1', 'Invoice exchange', 'Sending invoice',
|
||||
ErrorText, ErrorCallStack, CustomDimensions);
|
||||
end;
|
||||
|
||||
[TryFunction]
|
||||
local procedure TrySendInvoice()
|
||||
begin
|
||||
Error(SendFailedErr);
|
||||
end;
|
||||
|
||||
var
|
||||
SendFailedErr: Label 'The invoice could not be sent.';
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [18..]
|
||||
domain: privacy
|
||||
keywords: [featuretelemetry, logerror, errortext, errorcallstack, alerrortext, alerrorcallstack, customdimensions]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# FeatureTelemetry.LogError emits more than caller custom dimensions
|
||||
|
||||
## Description
|
||||
|
||||
`FeatureTelemetry.LogError` emits its `ErrorText` as the telemetry message and adds it as `alErrorText`. The overloads with `ErrorCallStack` also add `alErrorCallStack`. These dimensions are produced in addition to the caller-supplied `CustomDimensions` dictionary, and the Feature Telemetry implementation sends the event as `SystemMetadata`.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Review the dedicated error arguments as telemetry payload. Capture `GetLastErrorText(true)` when scrubbed platform error text is sufficient, and pass `GetLastErrorCallStack()` only as a call stack. Keep custom dimensions non-personal too.
|
||||
|
||||
See sample: `featuretelemetry-logerror-implicit-errortext.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Approving a `LogError` call because its explicit dictionary contains only safe values while it passes unsanitized `GetLastErrorText()` or arbitrary context through `ErrorText` or `ErrorCallStack`. Those arguments become telemetry dimensions outside the dictionary.
|
||||
|
||||
See sample: `featuretelemetry-logerror-implicit-errortext.bad.al`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue