- pages-must-not-contain-business-logic.good.al/.bad.al: the "good"
codeunit still directly assigned real Sales Line."Line Amount" and
called Modify(), bypassing the field's normal Validate cascade
(discount, VAT, related-amount maintenance) - persisting inconsistent
document lines regardless of which object the code lived in. Replaced
the real Sales Line example with a self-contained "Sample Order Line"
table and switched the codeunit to Validate()/Modify(true), so the
fixture demonstrates the page-vs-codeunit separation without teaching
unsafe direct field writes to a real BC document table.
- bcpt-scenarios-must-be-app-specific.good.al: Customer.FindFirst()
assumed a pre-existing customer (fails against an empty environment),
and a session-local NextNo counter for the header key collides across
concurrent BCPT sessions and repeated runs. Creates its own customer
when none exists, and generates keys from CreateGuid() instead of an
in-memory counter.
- upgrade-tag-logic-must-not-nest-deeply: the rule conflated two
different things - nesting one tag's existence check inside another
(the real anti-pattern Microsoft's guidance warns against) with
having business-data safety conditions inside a single tagged
migration's own loop body (which Microsoft's own worked example does,
and its own design guidance explicitly requires: "Implement extra
safety checks to avoid data corruption, even though you're using
upgrade tags"). Rewrote the Description/Best Practice/Anti Pattern to
scope the rule to actual tag nesting and migrations blended under one
tag, and rewrote both fixtures: good.al now shows two safety
conditions correctly nested inside one migration's own loop plus a
second, genuinely separate migration as its own flat tagged
procedure; bad.al now shows the real anti-pattern, one tag's check
nested inside another's guarded body.
- table-design-must-match-bc-table-type-conventions: the rule and its
worklist cue fired on any new table with a keys block, forcing
buffers, queues, logs, mapping tables, and staging tables into the
nearest-looking one of nine business-record archetypes. Added an
explicit scope note that these nine types aren't an exhaustive table
catalogue, and narrowed the al-data-modeling-review.md cue to require
positive evidence (a type-specific naming suffix, key shape, or
usage) before worklisting, instead of a bare keys/primary-key
declaration.
Six carried-over threads:
- api-page-least-privilege-write-access fixtures: added the mandatory
EntityName/EntitySetName properties (AL0485).
- pages-must-not-contain-business-logic fixtures: Sales Line has no
"Total Amount" field; replaced with the real "Line Amount" (field 103).
- test-feature-scenario-tags.good.al and test-one-when-per-test.good.al:
CreatePriceHeader leaves a price list in Draft status, which price
calculation ignores. Added Validate(Status, Active) + Modify before the
sales line that depends on it. Verified Status field/enum against
PriceListHeader.Table.al and PriceStatus.Enum.al in the BCApps clone.
- exposed-objects-must-be-in-a-permission-set.md: a published codeunit is
a SOAP endpoint (SOAP is deprecated), not OData - Page/Query are the
OData object types. Corrected and pointed new integrations at API
pages/queries instead.
- al-error-handling-review.md: the log-writes-must-survive-rollback cue
selected on Session.StartSession, which only appears in the compliant
fix, never in the anti-pattern - the bad fixture could never be
worklisted. Recued on the actual risk shape (log insert around a
failed TryFunction/GetLastError* path, then raise/propagate), with
StartSession as an explicit compliant discriminator instead.
- page-design-must-match-bc-page-type-conventions.md: the enum value is
NavigatePage, not Navigate; noted the type list is a selected subset,
not an exhaustive PageType catalogue (PromptDialog, ConfigurationDialog,
UserControlHost, XmlPort also exist, out of this article's scope).
Four new correctness gaps:
- release-must-update-app-version.md: "the version is the only identity"
was backwards - id is the app's stable identity, version identifies a
release/code-state of it.
- defensive-vs-offensive-code-must-match-blast-radius.good.al: the "low
blast radius" example had no else branch, so a failed Customer.Get()
left the field at its prior/default value instead of the explicit
chosen fallback the article claims to demonstrate. Added the else.
- bcpt-scenarios-must-be-app-specific.good.al: InitTest and both measured
StartScenario/EndScenario sections were empty/comment-only, so the
"app-specific" fixture measured no actual work. Filled in a real,
self-contained header+line creation path.
- upgrade-tag-logic-must-not-nest-deeply.good.al: the flattened version
dropped both safety conditions the bad fixture had (Discount % = 0,
nonblank posting group), silently changing behavior instead of just
removing nesting. Extracted the guarded update into a helper with both
conditions preserved as early exits.
Also converted this PR's remaining plain-backtick "See sample:" sample
references (16 articles) to the READ-convention markdown-link form,
matching the fix already made on #156/#158.
Rebased onto upstream/main (conflicts in al-ui-review.md, al-style-review.md,
al-upgrade-review.md against merged upstream PRs - all additive, both
sides' worklist cues retained).
Second contribution from CURABIS ApS, generalized from patterns observed across real AppSource/PTE development. Cross-checked against the current microsoft/knowledge corpus before opening; several originally-drafted candidates were dropped as duplicates of existing files.