mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-07 01:46:53 +01:00
Sync knowledge articles with review agent instructions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
f562fba837
commit
5bcdc55df9
62 changed files with 768 additions and 58 deletions
|
|
@ -0,0 +1,14 @@
|
|||
table 50936 "Migrated Employee"
|
||||
{
|
||||
fields
|
||||
{
|
||||
field(1; "Employee No."; Code[20])
|
||||
{
|
||||
DataClassification = ToBeClassified;
|
||||
}
|
||||
field(2; "Tax Identification No."; Text[30])
|
||||
{
|
||||
DataClassification = SystemMetadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
table 50935 "Migrated Employee"
|
||||
{
|
||||
fields
|
||||
{
|
||||
field(1; "Employee No."; Code[20])
|
||||
{
|
||||
DataClassification = EndUserPseudonymousIdentifiers;
|
||||
}
|
||||
field(2; "Tax Identification No."; Text[30])
|
||||
{
|
||||
DataClassification = EndUserIdentifiableInformation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: privacy
|
||||
keywords: [migration, dataclassification, hybrid, destination, pii]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Classify migrated data at the destination field
|
||||
|
||||
## Description
|
||||
|
||||
Hybrid migration codeunits such as HybridSL, HybridGP, and HybridBC legitimately process sensitive source data: tax IDs, employee identifiers, financial balances, and customer records. The privacy concern is not that the migration code touches the data. The concern is where the data lands: the destination table field must have a DataClassification value that matches the migrated content.
|
||||
|
||||
## Best Practice
|
||||
|
||||
When reviewing migration code, follow the assignment to the destination field and verify that the destination table declares an appropriate field-level or inherited DataClassification. Treat the migration procedure itself as expected business functionality; flag only missing or understated classification on the persistent destination.
|
||||
|
||||
See sample: `classify-data-at-migration-destination.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Flagging a migration procedure merely because it copies tax IDs or names from a source system. That creates false positives and misses the real issue: a destination field with no classification, `ToBeClassified`, or `SystemMetadata` for customer or employee data.
|
||||
|
||||
See sample: `classify-data-at-migration-destination.bad.al`.
|
||||
|
|
@ -15,12 +15,12 @@ The privacy concern with user-facing text is not what the authenticated user see
|
|||
|
||||
## Best Practice
|
||||
|
||||
Free-text business content — customer names, email addresses, document numbers — is acceptable in Message, Confirm, and Notification. Treat Error text as if it will be read by telemetry consumers, because it will be. Use localized Labels with the fewest possible PII placeholders, or system identifiers (SystemId, primary key values) rather than personal data.
|
||||
Free-text business content — customer names, email addresses, document numbers — is acceptable in Message, Confirm, and Notification. Treat Error text as if it will be read by telemetry consumers, because it will be, but use direct Error substitution rather than pre-building the message. `Error(MyErr, EmailAddress)` is telemetry-safe; `Error(StrSubstNo(..., EmailAddress))` is not.
|
||||
|
||||
See sample: `error-is-logged-to-telemetry-message-is-not.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Embedding customer emails, phone numbers, addresses, or names directly into Error strings — either as literals or via pre-built StrSubstNo output — because "the user will see this anyway." The user also sees Message and Confirm, but those are not logged. Error is.
|
||||
Embedding customer emails, phone numbers, addresses, or names as literals in an Error label or baking them into a Text value with StrSubstNo before calling Error. The user also sees Message and Confirm, but those are not logged. Error is logged, so dynamic customer data must stay as direct substitution arguments.
|
||||
|
||||
See sample: `error-is-logged-to-telemetry-message-is-not.bad.al`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
codeunit 50936 "Privacy FeatureTelemetry Bad"
|
||||
{
|
||||
procedure LogExpenseReleased(EmployeeNo: Code[20]; UserName: Text)
|
||||
var
|
||||
FeatureTelemetry: Codeunit "Feature Telemetry";
|
||||
CustomDimensions: Dictionary of [Text, Text];
|
||||
begin
|
||||
CustomDimensions.Add('EmployeeNo', EmployeeNo);
|
||||
CustomDimensions.Add('UserName', UserName);
|
||||
CustomDimensions.Add('LastError', GetLastErrorText());
|
||||
|
||||
FeatureTelemetry.LogUsage('0000EA1', 'Expense Agent', 'Document Released', CustomDimensions);
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
codeunit 50935 "Privacy FeatureTelemetry Good"
|
||||
{
|
||||
procedure LogExpenseReleased()
|
||||
var
|
||||
FeatureTelemetry: Codeunit "Feature Telemetry";
|
||||
CustomDimensions: Dictionary of [Text, Text];
|
||||
begin
|
||||
CustomDimensions.Add('DocumentType', 'Expense');
|
||||
CustomDimensions.Add('LineCountBucket', '10-20');
|
||||
|
||||
FeatureTelemetry.LogUsage('0000EA1', 'Expense Agent', 'Document Released', CustomDimensions);
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: privacy
|
||||
keywords: [featuretelemetry, customdimensions, telemetry, pii, customercontent]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Keep customer data out of FeatureTelemetry custom dimensions
|
||||
|
||||
## Description
|
||||
|
||||
`Codeunit "Feature Telemetry"` writes telemetry through methods such as `LogUsage`, `LogUptake`, and `LogError`. The `CustomDimensions` dictionary passed to those methods is exported to the telemetry pipeline, so it has the same privacy boundary as `Session.LogMessage` dimensions. Customer names, email addresses, employee numbers, user IDs, security IDs, notes, and `GetLastErrorText()` do not become safe merely because they are structured dimensions.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Log feature state, event names, counts, enum values, and non-personal technical identifiers. Omit customer and employee identifiers from `CustomDimensions`; if diagnostics need correlation, use a non-personal event ID or aggregate count instead.
|
||||
|
||||
See sample: `keep-customer-data-out-of-featuretelemetry-dimensions.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Adding employee numbers, user names, customer emails, free-text descriptions, or raw `GetLastErrorText()` to the `CustomDimensions` dictionary before calling `FeatureTelemetry.LogUsage`, `LogUptake`, or `LogError`.
|
||||
|
||||
See sample: `keep-customer-data-out-of-featuretelemetry-dimensions.bad.al`.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: privacy
|
||||
keywords: [tobeclassified, dataclassification, release, gdpr, placeholder]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Resolve ToBeClassified before release
|
||||
|
||||
## Description
|
||||
|
||||
`DataClassification = ToBeClassified` is a development marker, not a releasable privacy state. It tells reviewers and tooling that the field still needs classification work. Shipping it prevents data-subject, retention, and telemetry tooling from making a correct decision about the field.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Replace every `ToBeClassified` value with the narrowest accurate classification before the PR ships to customers. If the field inherits a correct table-level DataClassification, remove the placeholder rather than leaving a field-level `ToBeClassified` override.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Treating ToBeClassified as a safe default because the field is new or because the final classification is uncertain. Uncertainty should bias toward a stronger classification, not toward an unresolved placeholder.
|
||||
|
|
@ -11,7 +11,7 @@ application-area: [all]
|
|||
|
||||
## Description
|
||||
|
||||
`Session.LogMessage` accepts a DataClassification parameter that governs how the platform handles the logged content in the telemetry pipeline. Omitting it is a schema violation the platform cannot repair later. Embedding personal data — emails, names, phone numbers, addresses, filenames of user uploads — in the message string also defeats classification, because the pipeline sees opaque text and cannot selectively redact.
|
||||
`Session.LogMessage` accepts a DataClassification parameter that governs how the platform handles the logged content in the telemetry pipeline. Omitting it is a schema violation the platform cannot repair later. Embedding personal data — emails, names, phone numbers, addresses, filenames of user uploads — in the message string also defeats classification, because the pipeline sees opaque text and cannot selectively redact. The same privacy boundary applies to other telemetry surfaces such as `Codeunit "Feature Telemetry"` custom dimensions.
|
||||
|
||||
## Best Practice
|
||||
|
||||
|
|
|
|||
|
|
@ -11,11 +11,11 @@ application-area: [all]
|
|||
|
||||
## Description
|
||||
|
||||
Error messages are captured by platform telemetry. When Error receives a format template and field references as substitution arguments (Error('... %1 ...', Customer."No.")), the platform inspects each field's DataClassification and omits sensitive values from telemetry automatically. When the caller pre-builds the message with StrSubstNo and then passes the resulting Text to Error, the platform sees a plain string with no field context and logs the whole thing verbatim — any PII already baked in is exported to telemetry.
|
||||
Error messages are captured by platform telemetry. When Error receives a format template and substitution arguments directly (`Error('... %1 ...', Value)`), the platform can classify and strip sensitive values before telemetry is written. This is true whether the arguments are record fields, local variables, function results, or other expressions. When the caller pre-builds the message with StrSubstNo and then passes the resulting Text to Error, the platform sees a plain string with no argument context and logs the whole thing verbatim — any PII already baked in is exported to telemetry.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Pass the template and the field references directly to Error. Declare the template as a Label with a Comment describing each placeholder. The platform's field-aware classification logic then takes care of what reaches telemetry.
|
||||
Pass the template and substitution arguments directly to Error. Declare the template as a Label with a Comment describing each placeholder. Do not flag direct Error substitution merely because an argument may contain a customer name, email address, or phone number; the platform intercepts those arguments before telemetry.
|
||||
|
||||
See sample: `strsubstno-prebuild-breaks-error-telemetry-classification.good.al`.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue