Merge upstream/main; adopt articles[] eval override, add sample-link READ convention

- Resolve conflicts in al-data-modeling-review.md by keeping both sides'
  additions (folder-path support, InitRecord/Round cues from upstream;
  the 9 document-distribution/pricing/barcode cues from this branch).
- Switch the data-modeling evaluation override from an ad-hoc
  additionalArticles field to upstream's now-established articles[]
  convention (used elsewhere for finance/scm/query/reporting/style),
  removing the redundant parallel code path from Test-ReviewFixtures.ps1.
- Fix all 9 new articles' sample references to the markdown-link READ
  convention required by Knowledge-Retrieval.ps1's Assert-SampleLink
  (plain backticks satisfy validate_frontmatter.py's regex alone but not
  this stricter check - both validators must pass).

Validators: frontmatter 0/0, review-fixtures 126 cases/20 domains PASSED,
knowledge-index 342 articles/575 samples PASSED, skill-index 19 review
leaves PASSED.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Michael Dieringer 2026-09-24 06:41:24 +02:00
commit e22352b248
504 changed files with 9501 additions and 1969 deletions

View file

@ -40,7 +40,7 @@ relevant `Method` (e.g. `"Lowest Price"`), `Type` (`Sale`/`Purchase`), and
`Asset Type` — with `Default := true`, since `FindSetup` only considers
rows where `Default` is set when resolving a handler for a line.
See sample: `activate-new-price-calculation-handler-via-onfindsupportedsetup.good.al`.
See sample: [`activate-new-price-calculation-handler-via-onfindsupportedsetup.good.al`](activate-new-price-calculation-handler-via-onfindsupportedsetup.good.al).
## Anti Pattern
@ -51,7 +51,7 @@ selected manually if a user creates their own `Price Calculation Setup`
row through the UI — but ships with no default row, so it's never active
for anyone until someone notices it's missing and configures it by hand.
See sample: `activate-new-price-calculation-handler-via-onfindsupportedsetup.bad.al`.
See sample: [`activate-new-price-calculation-handler-via-onfindsupportedsetup.bad.al`](activate-new-price-calculation-handler-via-onfindsupportedsetup.bad.al).
## Source

View file

@ -19,10 +19,10 @@ Putting the block check inside the master's own `OnInsert`/`OnModify` does nothi
The referencing line validates `Master.TestField(Blocked, false)` in `OnValidate` of the reference field and re-checks before posting. The master table stays logic-free on `Blocked`.
See sample: `check-blocked-in-referencing-code-not-in-master.good.al`.
See sample: [`check-blocked-in-referencing-code-not-in-master.good.al`](check-blocked-in-referencing-code-not-in-master.good.al).
## Anti Pattern
The block check sits in the master's own `OnModify`/`OnInsert` (so referencing and posting proceed unchecked), or there is no check at all on the referencing side.
See sample: `check-blocked-in-referencing-code-not-in-master.bad.al`.
See sample: [`check-blocked-in-referencing-code-not-in-master.bad.al`](check-blocked-in-referencing-code-not-in-master.bad.al).

View file

@ -40,7 +40,7 @@ what validation must pass before sending — is genuinely specific to the
document. Custom logic belongs around the call to `Report Selections`,
not instead of it.
See sample: `custom-document-dispatch-must-not-bypass-report-selections.good.al`.
See sample: [`custom-document-dispatch-must-not-bypass-report-selections.good.al`](custom-document-dispatch-must-not-bypass-report-selections.good.al).
## Anti Pattern
@ -51,7 +51,7 @@ without a code change and a new release, and the document is invisible to
"Document Layouts" — the standard place every other document's
distribution is configured.
See sample: `custom-document-dispatch-must-not-bypass-report-selections.bad.al`.
See sample: [`custom-document-dispatch-must-not-bypass-report-selections.bad.al`](custom-document-dispatch-must-not-bypass-report-selections.bad.al).
## Source

View file

@ -61,7 +61,7 @@ equally correct; neither reads the counterparty's assigned profile.
Reserve a genuine `Get`/`GetDefaultForCustomer`/`GetDefaultForVendor`
lookup and `Send`/`SendVendor` for Post-and-Send.
See sample: `document-print-and-email-actions-call-report-selections-directly.good.al`.
See sample: [`document-print-and-email-actions-call-report-selections-directly.good.al`](document-print-and-email-actions-call-report-selections-directly.good.al).
## Anti Pattern
@ -76,7 +76,7 @@ clicking "Email" does nothing observable. A second version of the same
mistake: an email action on a document that only receives from its
counterparty and was never meant to send anything back.
See sample: `document-print-and-email-actions-call-report-selections-directly.bad.al`.
See sample: [`document-print-and-email-actions-call-report-selections-directly.bad.al`](document-print-and-email-actions-call-report-selections-directly.bad.al).
## Source

View file

@ -59,7 +59,7 @@ custom table that should be searchable by document number:
uncombined keys, with no compound key between them, because `"No."`
alone is already sufficient.
See sample: `extend-find-entries-navigate-for-new-document-types.good.al`.
See sample: [`extend-find-entries-navigate-for-new-document-types.good.al`](extend-find-entries-navigate-for-new-document-types.good.al).
## Anti Pattern
@ -70,7 +70,7 @@ and a correct record count both show up — but leads nowhere when
selected, with no error and no indication to the user that anything is
wrong.
See sample: `extend-find-entries-navigate-for-new-document-types.bad.al`.
See sample: [`extend-find-entries-navigate-for-new-document-types.bad.al`](extend-find-entries-navigate-for-new-document-types.bad.al).
## Source

View file

@ -39,7 +39,7 @@ price list, extend `Price Source Type` and the matching document subset
enum (`Sales Price Source Type`, `Purchase Price Source Type`, `Job Price
Source Type`) together, using the identical numeric ID in both.
See sample: `extend-price-source-type-must-sync-document-subset-enum.good.al`.
See sample: [`extend-price-source-type-must-sync-document-subset-enum.good.al`](extend-price-source-type-must-sync-document-subset-enum.good.al).
## Anti Pattern
@ -50,7 +50,7 @@ absent from the "Applies-to Type" options on an actual sales price list,
with no error anywhere: the base enum extension compiles and installs
cleanly on its own.
See sample: `extend-price-source-type-must-sync-document-subset-enum.bad.al`.
See sample: [`extend-price-source-type-must-sync-document-subset-enum.bad.al`](extend-price-source-type-must-sync-document-subset-enum.bad.al).
## Source

View file

@ -51,7 +51,7 @@ legitimately, since that document posts to both ledgers, not by default.
triad and the value is unreachable in Document Layouts; wire both sides
needlessly and the picker is cluttered with a value that never applies.
See sample: `extend-report-selection-usage-for-new-document-types.good.al`
See sample: [`extend-report-selection-usage-for-new-document-types.good.al`](extend-report-selection-usage-for-new-document-types.good.al)
(customer-only document — only the customer-side enum and triad added).
## Anti Pattern
@ -60,7 +60,7 @@ See sample: `extend-report-selection-usage-for-new-document-types.good.al`
subscribers. Works via the tenant-wide default, so it's invisible in
testing — but Document Layouts shows the value's rows blank, can't offer
it in the Usage dropdown, and "Copy from Report Selection" never lists
it. See sample: `extend-report-selection-usage-for-new-document-types.bad.al`.
it. See sample: [`extend-report-selection-usage-for-new-document-types.bad.al`](extend-report-selection-usage-for-new-document-types.bad.al).
2. Subscribe both counterparties' triads for a one-sided document. This is
the overbroad default Jesper Schulz-Wedde's review caught: it
contradicts how `ReportSelectionHandlerCZZ` actually partitions its

View file

@ -0,0 +1,28 @@
table 50603 "Sample Order Header Bad"
{
fields
{
field(1; "No."; Code[20])
{
DataClassification = CustomerContent;
}
field(2; "Document Date"; Date)
{
DataClassification = CustomerContent;
}
}
trigger OnInsert()
var
SalesSetup: Record "Sales & Receivables Setup";
NoSeries: Codeunit "No. Series";
begin
"Document Date" := WorkDate();
if "No." = '' then begin
SalesSetup.Get();
SalesSetup.TestField("Order Nos.");
"No." := NoSeries.GetNextNo(SalesSetup."Order Nos.");
end;
end;
}

View file

@ -0,0 +1,45 @@
table 50602 "Sample Order Header Good"
{
fields
{
field(1; "No."; Code[20])
{
DataClassification = CustomerContent;
}
field(2; "Document Date"; Date)
{
DataClassification = CustomerContent;
}
}
trigger OnInsert()
var
SalesSetup: Record "Sales & Receivables Setup";
NoSeries: Codeunit "No. Series";
begin
if "No." = '' then begin
SalesSetup.Get();
SalesSetup.TestField("Order Nos.");
"No." := NoSeries.GetNextNo(SalesSetup."Order Nos.");
end;
InitRecord();
end;
procedure InitRecord()
begin
OnBeforeInitRecord(Rec);
"Document Date" := WorkDate();
OnAfterInitRecord(Rec);
end;
[IntegrationEvent(false, false)]
local procedure OnBeforeInitRecord(var SampleOrderHeader: Record "Sample Order Header Good")
begin
end;
[IntegrationEvent(false, false)]
local procedure OnAfterInitRecord(var SampleOrderHeader: Record "Sample Order Header Good")
begin
end;
}

View file

@ -0,0 +1,30 @@
---
bc-version: [all]
domain: data-modeling
keywords: [document-header, initrecord, number-series, default-values, oninsert, initialization]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Initialize document defaults in `InitRecord` after assigning the number
## Description
Business Central document headers assign their number series first and then call an `InitRecord` procedure that owns the remaining business defaults, such as posting and document dates. Keeping that sequence and extensibility point makes initialization consistent for every creation path and lets extensions subscribe around one documented operation. Defaults scattered across page triggers or unrelated helpers can differ between UI, API, test, and background creation.
## Best Practice
In the document table's insert path, assign the document number and then call `InitRecord`. Keep the default assignments in that procedure and expose narrow before/after events when other extensions must participate.
See sample: [`initialize-document-defaults-in-initrecord.good.al`](initialize-document-defaults-in-initrecord.good.al).
## Anti Pattern
Assigning document defaults in a page trigger, or scattering them directly through `OnInsert` with no `InitRecord` boundary. Non-page creation paths can then miss the defaults, and extensions have no stable initialization hook.
See sample: [`initialize-document-defaults-in-initrecord.bad.al`](initialize-document-defaults-in-initrecord.bad.al).
## Reference
[Use the InitRecord function](https://learn.microsoft.com/en-us/training/modules/use-document-standards-business-central/3-use-initrecord-function)

View file

@ -19,10 +19,10 @@ This is not an `Integer` `AutoIncrement` key, a GUID, or the `SystemId`. Those a
`No.` `Code[20]` is the sole primary key; a non-editable `No. Series` `Code[20]` field records the source series. `OnInsert` checks `if "No." = ''`, reads the setup table, `TestField`s the configured series, stores it in `No. Series`, and assigns `No.` from the series.
See sample: `master-table-no-from-number-series-in-oninsert.good.al`.
See sample: [`master-table-no-from-number-series-in-oninsert.good.al`](master-table-no-from-number-series-in-oninsert.good.al).
## Anti Pattern
An `Integer` `AutoIncrement` (or GUID / `SystemId`) primary key used as the business key, with no `OnInsert` number assignment. Records get an opaque identifier no user can reference, and the master no longer participates in the standard numbering and manual-entry behavior every other BC master follows.
See sample: `master-table-no-from-number-series-in-oninsert.bad.al`.
See sample: [`master-table-no-from-number-series-in-oninsert.bad.al`](master-table-no-from-number-series-in-oninsert.bad.al).

View file

@ -42,7 +42,7 @@ a trigger on the field itself (its own `OnValidate`, or a matching
`OnAfterValidate` integration event) that calls
`SalesLine.UpdateUnitPriceByField(SalesLine.FieldNo(<TheField>))`.
See sample: `new-price-source-must-add-candidate-and-trigger-recalculation.good.al`.
See sample: [`new-price-source-must-add-candidate-and-trigger-recalculation.good.al`](new-price-source-must-add-candidate-and-trigger-recalculation.good.al).
## Anti Pattern
@ -52,7 +52,7 @@ validation. The field is a genuine, working calculation candidate — new
lines price correctly — but editing the field on an existing line leaves
the unit price stale, with nothing to indicate why.
See sample: `new-price-source-must-add-candidate-and-trigger-recalculation.bad.al`.
See sample: [`new-price-source-must-add-candidate-and-trigger-recalculation.bad.al`](new-price-source-must-add-candidate-and-trigger-recalculation.bad.al).
## Source

View file

@ -25,7 +25,7 @@ See also `validate-table-relation-false-suppresses-rename-propagation.md` for th
The owning table implements `OnDelete` and deletes its dependents there, filtered on the foreign key. Declare `Permissions = tabledata <dependent> = rd` on the owning table — granting delete rights only on the parent is a common miss that makes the trigger fail for a non-`SUPER` user. This mirrors the base application, where every header table deletes its own lines.
See sample: `owning-table-must-delete-dependents-in-ondelete.good.al`.
See sample: [`owning-table-must-delete-dependents-in-ondelete.good.al`](owning-table-must-delete-dependents-in-ondelete.good.al).
## Anti Pattern
@ -33,4 +33,4 @@ A parent table with dependent rows and no `OnDelete` trigger, where the dependen
Detection signal: a table declares `TableRelation` to table X, and table X has no `OnDelete` trigger. Whether a delete path currently exists in the UI is irrelevant to the finding.
See sample: `owning-table-must-delete-dependents-in-ondelete.bad.al`.
See sample: [`owning-table-must-delete-dependents-in-ondelete.bad.al`](owning-table-must-delete-dependents-in-ondelete.bad.al).

View file

@ -57,7 +57,7 @@ font name to specify is literally `IDAutomation2D` (Maxicode itself uses
purchased version name for that specific font (e.g. `IDAutomationHC39M`
for Code 39), never a name containing `Demo`.
See sample: `report-barcodes-must-use-barcode-module-and-production-font-name.good.al`.
See sample: [`report-barcodes-must-use-barcode-module-and-production-font-name.good.al`](report-barcodes-must-use-barcode-module-and-production-font-name.good.al).
## Anti Pattern
@ -75,7 +75,7 @@ in review and testing and fails silently — the first because the encoded
data was never a real barcode, the second because Business Central
online refuses to render it at all.
See sample: `report-barcodes-must-use-barcode-module-and-production-font-name.bad.al`.
See sample: [`report-barcodes-must-use-barcode-module-and-production-font-name.bad.al`](report-barcodes-must-use-barcode-module-and-production-font-name.bad.al).
## Source

View file

@ -0,0 +1,8 @@
codeunit 50601 "Directed Rounding Bad"
{
procedure FloorAmount(Value: Decimal; Precision: Decimal): Decimal
begin
// For negative values, '<' rounds toward zero rather than toward negative infinity.
exit(Round(Value, Precision, '<'));
end;
}

View file

@ -0,0 +1,10 @@
codeunit 50600 "Directed Rounding Good"
{
procedure RoundAmount(Value: Decimal; Precision: Decimal; IncreaseMagnitude: Boolean): Decimal
begin
if IncreaseMagnitude then
exit(Round(Value, Precision, '>'));
exit(Round(Value, Precision, '<'));
end;
}

View file

@ -0,0 +1,30 @@
---
bc-version: [all]
domain: data-modeling
keywords: [round, rounding, direction, precision, negative-decimal, amount]
technologies: [al]
countries: [w1]
application-area: [all]
---
# `Round` direction symbols follow magnitude, not mathematical ordering
## Description
AL's `Round(Number, Precision, Direction)` uses `'>'` to round away from zero and `'<'` to round toward zero. For a negative value this reverses mathematical ordering: `Round(-1234.56789, 0.001, '<')` returns `-1234.567`, while direction `'>'` returns `-1234.568`. Code that treats the symbols as mathematical ceiling and floor produces sign-dependent amount errors, commonly on credit documents and negative adjustments.
## Best Practice
Choose the direction from the business meaning: `'>'` increases absolute magnitude and `'<'` decreases absolute magnitude for both positive and negative values. Include positive and negative cases whenever a directed rounding rule is tested.
See sample: [`round-direction-symbols-use-magnitude.good.al`](round-direction-symbols-use-magnitude.good.al).
## Anti Pattern
Using `'<'` as a mathematical floor or `'>'` as a mathematical ceiling. The result looks correct for positive amounts but moves in the opposite mathematical direction for negative amounts.
See sample: [`round-direction-symbols-use-magnitude.bad.al`](round-direction-symbols-use-magnitude.bad.al).
## Reference
[Use the Round function](https://learn.microsoft.com/en-us/training/modules/use-document-standards-business-central/4a-use-round-function)

View file

@ -19,10 +19,10 @@ The reason is a BC-specific trap: renaming a record changes its primary key and
Both `OnModify` and `OnRename` set `"Last Date Modified" := Today();`, and the field is declared `Editable = false` so only the triggers maintain it.
See sample: `set-last-date-modified-in-onmodify-and-onrename.good.al`.
See sample: [`set-last-date-modified-in-onmodify-and-onrename.good.al`](set-last-date-modified-in-onmodify-and-onrename.good.al).
## Anti Pattern
Only `OnModify` assigns `Last Date Modified`. After a rename the value is stale, and any process that trusts it to detect changes misses the record.
See sample: `set-last-date-modified-in-onmodify-and-onrename.bad.al`.
See sample: [`set-last-date-modified-in-onmodify-and-onrename.bad.al`](set-last-date-modified-in-onmodify-and-onrename.bad.al).

View file

@ -19,10 +19,10 @@ The setup **card** page enforces the singleton: `InsertAllowed = false` and `Del
`Primary Key` `Code[10]` is the sole key; the setup is surfaced through a Card page with `InsertAllowed = false`, `DeleteAllowed = false`, and an open-time guard that inserts the blank row if it is missing.
See sample: `setup-table-is-a-singleton.good.al`.
See sample: [`setup-table-is-a-singleton.good.al`](setup-table-is-a-singleton.good.al).
## Anti Pattern
An `Integer` / `AutoIncrement` key, a page that allows insert or delete, or a List page over the setup table. Any of these lets the table hold zero or many rows, so "the setup" becomes ambiguous and `Get()` may fail or read the wrong record.
See sample: `setup-table-is-a-singleton.bad.al`.
See sample: [`setup-table-is-a-singleton.bad.al`](setup-table-is-a-singleton.bad.al).

View file

@ -17,10 +17,10 @@ application-area: [all]
When sharing media between different tables, iterate the source `MediaSet` and call `Target.MediaSetField.Insert(Source.MediaSetField.Item(Index))`, then modify the target record. Direct field assignment is safe only when source and target are the same record subtype and use the same field ID. This concern is about reference/delete integrity, not the separate performance cost of `ModifyAll` on tables with media fields.
See sample: `share-mediaset-items-with-insert-not-field-assignment.good.al`.
See sample: [`share-mediaset-items-with-insert-not-field-assignment.good.al`](share-mediaset-items-with-insert-not-field-assignment.good.al).
## Anti Pattern
`Target.Picture := Source.Picture;` where the two variables refer to different table types or different media-field IDs. The code copies an opaque ID, but the platform does not know that two independent fields now share the media object.
See sample: `share-mediaset-items-with-insert-not-field-assignment.bad.al`.
See sample: [`share-mediaset-items-with-insert-not-field-assignment.bad.al`](share-mediaset-items-with-insert-not-field-assignment.bad.al).

View file

@ -17,10 +17,10 @@ A `tableextension` can add to an existing `TableRelation`, but the combined rela
When a relation is designed to follow an extensible enum, express the base cases as conditional branches and leave no unconditional catch-all ahead of future extension branches. An enum extension can then append a condition for its new value. When extending a field you do not own, inspect the original `TableRelation`; do not claim that an appended condition overrides an unconditional relation.
See sample: `table-relation-extensions-are-additive-and-top-down.good.al`.
See sample: [`table-relation-extensions-are-additive-and-top-down.good.al`](table-relation-extensions-are-additive-and-top-down.good.al).
## Anti Pattern
A base field has an unconditional `TableRelation = Customer;` and a `tableextension` adds `if (Type = const(Resource)) Resource`. The original unconditional branch always wins, so the new enum value still validates and looks up against Customer. The concern is evaluation order, not `ValidateTableRelation`; free-form input is covered separately by security guidance.
See sample: `table-relation-extensions-are-additive-and-top-down.bad.al`.
See sample: [`table-relation-extensions-are-additive-and-top-down.bad.al`](table-relation-extensions-are-additive-and-top-down.bad.al).

View file

@ -53,7 +53,7 @@ consistency requirement between definitions that are already meant to be
linked, not a mandate to check every field against every table on the
cascade.
See sample: `transferfields-mirrored-fields-must-match-type-and-length.good.al`.
See sample: [`transferfields-mirrored-fields-must-match-type-and-length.good.al`](transferfields-mirrored-fields-must-match-type-and-length.good.al).
## Anti Pattern
@ -64,7 +64,7 @@ incompatible data type — on one side. Both definitions compile without
error; nothing fails until an actual value exceeds the shorter one, which
typical test data never does.
See sample: `transferfields-mirrored-fields-must-match-type-and-length.bad.al`.
See sample: [`transferfields-mirrored-fields-must-match-type-and-length.bad.al`](transferfields-mirrored-fields-must-match-type-and-length.bad.al).
## Source

View file

@ -17,10 +17,10 @@ application-area: [all]
Use `TransferFields(Source)` only when every field the destination requires, including primary key fields, is guaranteed to share a matching field number and type with the source; this form defaults `InitPrimaryKeyFields` to `true`. Fields with no matching field number, and fields whose types differ across extensions, are skipped regardless of `SkipFieldsNotMatchingType` — that parameter only governs same-extension type mismatches. If the destination depends on a field that falls into either case, map and validate it explicitly in code rather than relying on `TransferFields` to catch the gap. Use `SkipFieldsNotMatchingType = true` only when skipping same-extension type mismatches is an intentional, documented part of the transfer contract.
See sample: `transferfields-skip-type-mismatch-can-drop-data.good.al`.
See sample: [`transferfields-skip-type-mismatch-can-drop-data.good.al`](transferfields-skip-type-mismatch-can-drop-data.good.al).
## Anti Pattern
Using `TransferFields(Source, InitPrimaryKeyFields, true)` as a generic way to make two evolving table schemas transfer without errors, when the destination depends on every required source field being copied. A type change on either table can turn a previously transferred field into a silently skipped one without making the transfer itself fail.
See sample: `transferfields-skip-type-mismatch-can-drop-data.bad.al`.
See sample: [`transferfields-skip-type-mismatch-can-drop-data.bad.al`](transferfields-skip-type-mismatch-can-drop-data.bad.al).

View file

@ -19,10 +19,10 @@ LLMs reproduce the legacy `NoSeriesManagement` pattern because it dominates pre-
`OnInsert` assigns the number with `NoSeries.GetNextNo("No. Series")` where `NoSeries` is `Codeunit "No. Series"`. The `No.` field's `OnValidate` guards manual entry by calling `NoSeries.IsManual(...)` (or `TestManual`) before clearing `No. Series`.
See sample: `use-no-series-codeunit-not-noseriesmanagement.good.al`.
See sample: [`use-no-series-codeunit-not-noseriesmanagement.good.al`](use-no-series-codeunit-not-noseriesmanagement.good.al).
## Anti Pattern
`NoSeriesMgt.InitSeries(...)` for assignment and `NoSeriesMgt.TestManual(...)` for the manual check, where `NoSeriesMgt` is `Codeunit NoSeriesManagement`. Both are obsolete-pending and emit compiler warnings.
See sample: `use-no-series-codeunit-not-noseriesmanagement.bad.al`.
See sample: [`use-no-series-codeunit-not-noseriesmanagement.bad.al`](use-no-series-codeunit-not-noseriesmanagement.bad.al).

View file

@ -27,7 +27,7 @@ See also `owning-table-must-delete-dependents-in-ondelete.md` for the delete hal
Leave `ValidateTableRelation` at its default wherever the stored value must stay correct across a rename. When it must be disabled, or when the relationship cannot be expressed as a `TableRelation` at all, the table owning the referenced key carries an explicit `OnRename` that repoints the dependents itself.
See sample: `validate-table-relation-false-suppresses-rename-propagation.good.al`.
See sample: [`validate-table-relation-false-suppresses-rename-propagation.good.al`](validate-table-relation-false-suppresses-rename-propagation.good.al).
## Anti Pattern
@ -35,4 +35,4 @@ See sample: `validate-table-relation-false-suppresses-rename-propagation.good.al
Detection signal: any `ValidateTableRelation = false` on a field that also declares a `TableRelation`. Ask what repoints the value when the target is renamed; if the answer is "the platform", the finding stands.
See sample: `validate-table-relation-false-suppresses-rename-propagation.bad.al`.
See sample: [`validate-table-relation-false-suppresses-rename-propagation.bad.al`](validate-table-relation-false-suppresses-rename-propagation.bad.al).

View file

@ -25,7 +25,7 @@ See also `validate-table-relation-false-suppresses-rename-propagation.md`, which
Use `xRec` for the previous key in `OnRename`, and for the record being removed in `OnDelete`. In `OnModify`, obtain the before-image by re-reading the stored row rather than trusting `xRec`, so the logic behaves identically whether a page, a job queue or an API drove the write.
See sample: `xrec-is-a-before-image-only-in-some-triggers.good.al`.
See sample: [`xrec-is-a-before-image-only-in-some-triggers.good.al`](xrec-is-a-before-image-only-in-some-triggers.good.al).
## Anti Pattern
@ -33,4 +33,4 @@ Comparing `Rec` against `xRec` inside `OnModify` (or `OnInsert`) to detect a cha
Detection signal: any read of `xRec` inside `OnModify` or `OnInsert`. Treat "but it works when I test it on the page" as confirmation of the defect rather than a refutation.
See sample: `xrec-is-a-before-image-only-in-some-triggers.bad.al`.
See sample: [`xrec-is-a-before-image-only-in-some-triggers.bad.al`](xrec-is-a-before-image-only-in-some-triggers.bad.al).