Sync knowledge articles with review agent instructions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Jesper Schulz-Wedde 2026-05-05 14:08:32 +02:00
parent f562fba837
commit 5bcdc55df9
62 changed files with 768 additions and 58 deletions

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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`.

View file

@ -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`.

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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`.

View file

@ -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.

View file

@ -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

View file

@ -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`.