mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-06 09:26:52 +01:00
Improve PR-review parity: suggestion blocks, missing KB articles, privacy cross-ref
Adds the contract field, skill instructions, and two knowledge articles
that BCAppsBCQuality's PR-review agent needs to match (and exceed) the
coverage of the embedded review agent in BCAppsCampAIRHack:
skills/do.md
- New optional findings[].suggested-code field. Documents what it MUST
contain (a literal line-replacement payload) and when to emit it.
microsoft/skills/review/al-code-review.md
- Instructs both the agent self-review pass and rolled-up sub-skill
findings to populate suggested-code when the fix is mechanical.
- Lists examples (dead code removal, Count > 0 -> IsEmpty, object-scope
Label) that map to issues observed in the parity comparison.
microsoft/knowledge/style/telemetry-event-id-stable-unique.{md,bad.al,good.al}
- New knowledge article: telemetry event IDs must be stable, unique,
and non-placeholder. Closes a gap surfaced by the parity comparison.
microsoft/knowledge/style/labels-declared-at-object-scope.{md,bad.al,good.al}
- New knowledge article: Labels must live in the object-level var
block, not in procedure-local var blocks. Closes the second gap.
microsoft/knowledge/privacy/no-pii-in-telemetry-message-string.md
- Adds an explicit note that changing DataClassification alone does not
make embedding PII into the message string acceptable, plus links to
the two adjacent privacy articles. Resolves the privacy advice the
parity comparison flagged as ambiguous.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
4d59fb73bc
commit
b11f3ec506
9 changed files with 130 additions and 1 deletions
|
|
@ -17,8 +17,15 @@ application-area: [all]
|
|||
|
||||
Keep the telemetry message a static, non-personal string ("Customer record processed", "Error processing uploaded file"). When structured context is genuinely needed, attach it through custom dimensions, where individual values can be reviewed and classified at the dimension level rather than baked into a free-text message.
|
||||
|
||||
If a pseudonymous identifier (record `No.`, primary key value) genuinely belongs in the diagnostic, prefer attaching it through a custom dimension and set the call's `DataClassification` to match the data actually shipped — `EndUserPseudonymousIdentifiers` for pseudonymous IDs, `CustomerContent` for content-bearing telemetry. Changing the `DataClassification` alone does **not** make embedding a customer name into the message string acceptable; the data still ships in the message, and downstream consumers still see the literal string.
|
||||
|
||||
See sample: `no-pii-in-telemetry-message-string.good.al`.
|
||||
|
||||
## Related
|
||||
|
||||
- `session-logmessage-requires-dataclassification.md` — every `Session.LogMessage` call must specify `DataClassification`; choose the value to match the actual data shipped, not the value that makes the entry retained the longest.
|
||||
- `featuretelemetry-customdimensions-no-pii.md` — custom dimensions are the right surface for structured context, but they have their own PII rules.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
`Session.LogMessage('0000', StrSubstNo('Processed %1', Customer.Name), ...)` — the customer name is in telemetry the moment the line runs. Detection signal: a `StrSubstNo` whose result is the second argument of `Session.LogMessage`. The same shape with `FileName`, `EmployeeCode`, or any record field is the same problem.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
codeunit 50262 "Sample Label Scope Bad"
|
||||
{
|
||||
procedure LookupCustomer(CustomerNo: Code[20])
|
||||
var
|
||||
Customer: Record Customer;
|
||||
GreetingMsg: Label 'Hello %1', Comment = '%1 = Customer Name';
|
||||
begin
|
||||
if Customer.Get(CustomerNo) then
|
||||
Message(GreetingMsg, Customer.Name);
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
codeunit 50263 "Sample Label Scope Good"
|
||||
{
|
||||
var
|
||||
GreetingMsg: Label 'Hello %1', Comment = '%1 = Customer Name';
|
||||
|
||||
procedure LookupCustomer(CustomerNo: Code[20])
|
||||
var
|
||||
Customer: Record Customer;
|
||||
begin
|
||||
if Customer.Get(CustomerNo) then
|
||||
Message(GreetingMsg, Customer.Name);
|
||||
end;
|
||||
}
|
||||
30
microsoft/knowledge/style/labels-declared-at-object-scope.md
Normal file
30
microsoft/knowledge/style/labels-declared-at-object-scope.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: style
|
||||
keywords: [label, scope, procedure, translation, localization, xliff]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Declare Labels at object scope, not inside procedure `var` blocks
|
||||
|
||||
## Description
|
||||
|
||||
`Label` is the AL declaration that participates in the translation pipeline: the build extracts every Label declared in an object into the `.xlf` file shipped to translators, and the runtime substitutes the localized value when the object is loaded. Translation tooling discovers Labels by walking the object's top-level declarations.
|
||||
|
||||
Labels declared inside a procedure-local `var` block are still **compiled** as Label values, but their participation in localization is fragile: depending on the BC version, the build pipeline, and the translation toolchain in use, procedure-local Labels may be missed during XLIFF extraction, may be re-emitted with auto-generated keys that change between builds, or may not be addressable by reviewers triaging translations. The reliable, supported pattern is to declare every Label in the object's top-level `var` block.
|
||||
|
||||
The same rule applies to all object types that own behavior: codeunits, pages, tables, reports, queries, and their extensions. For shared messages used by multiple objects, declare the Label in the most appropriate owning object and reference it — do not duplicate the literal across procedure-scoped declarations in several places.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Move every `Label` to the object's top-level `var` block. Use the appropriate suffix (`Msg`, `Err`, `Qst`, `Lbl`, `Tok`, `Txt`) on the variable name so reviewers and the translation team can see at a glance what role the string plays. Pair non-translatable strings (URLs, JSON/XML fragments, integration tokens) with `Locked = true`, as covered by `label-locked-for-non-translatable.md`.
|
||||
|
||||
See sample: `labels-declared-at-object-scope.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Declaring `Label` inside a procedure-local `var` block — `procedure Lookup() var GreetingMsg: Label 'Hello %1';` — couples the translatable string to one procedure, hides it from object-level review, and depends on a translation pipeline behavior that is not part of the AL language contract.
|
||||
|
||||
See sample: `labels-declared-at-object-scope.bad.al`.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
codeunit 50260 "Sample Telemetry Id Bad"
|
||||
{
|
||||
procedure LogCustomerProcessed(var Customer: Record Customer)
|
||||
begin
|
||||
Session.LogMessage(
|
||||
'0000',
|
||||
'Customer record processed',
|
||||
Verbosity::Normal,
|
||||
DataClassification::SystemMetadata,
|
||||
TelemetryScope::All,
|
||||
'Category', 'QualitySamples');
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
codeunit 50261 "Sample Telemetry Id Good"
|
||||
{
|
||||
procedure LogCustomerProcessed(var Customer: Record Customer)
|
||||
begin
|
||||
Session.LogMessage(
|
||||
'QS0001',
|
||||
'Customer record processed',
|
||||
Verbosity::Normal,
|
||||
DataClassification::SystemMetadata,
|
||||
TelemetryScope::All,
|
||||
'Category', 'QualitySamples');
|
||||
end;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: style
|
||||
keywords: [telemetry, logmessage, event-id, sessionlogmessage, observability]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Telemetry event IDs must be stable, unique, and non-placeholder
|
||||
|
||||
## Description
|
||||
|
||||
The first parameter of `Session.LogMessage` is the **event ID**. Telemetry consumers — Application Insights queries, KQL dashboards, alert rules, support runbooks — pivot on this ID to filter and aggregate events. The contract works only when the ID is:
|
||||
|
||||
- **Stable** across releases: the same logical event keeps the same ID, so existing queries continue to match it.
|
||||
- **Unique** within the extension's telemetry catalogue: two different events MUST NOT share an ID, or downstream consumers cannot distinguish them.
|
||||
- **Non-placeholder**: literal IDs like `'0000'`, `'1234'`, `'TODO'`, or `'XX0000'` are placeholders that collide with other placeholder-using extensions, are unsearchable, and indicate the catalogue entry was never registered.
|
||||
|
||||
The convention used by Microsoft first-party AL code is a short prefix identifying the publisher or feature followed by a numeric suffix — for example `'AL0001'`, `'CUST0042'`, `'SHPFY-0007'`. The exact format is up to the extension; the requirements are stability, uniqueness, and that the chosen ID is registered in whatever catalogue or wiki the extension's telemetry consumers reference.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Assign each `Session.LogMessage` call a real, registered event ID drawn from the extension's catalogue. Treat the ID as part of the public contract of the event — renaming it is a breaking change for consumers. Keep IDs short, deterministic, and free of personal or environment-specific tokens.
|
||||
|
||||
See sample: `telemetry-event-id-stable-unique.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Calling `Session.LogMessage('0000', ...)` (or `'1234'`, `'TODO'`, an empty string, a GUID generated at runtime, or any other placeholder) leaves the event unsearchable and indistinguishable from every other event using the same placeholder. The catalogue entry never gets created because the developer "will fix it later", and the placeholder ships.
|
||||
|
||||
See sample: `telemetry-event-id-stable-unique.bad.al`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue