mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-06 17:36:53 +01:00
Complete AL review knowledge readiness
Fill telemetry and Query coverage, strengthen thin review domains, correct audited content defects, and add deterministic cheap-model evaluation and reference-integrity safeguards. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9825b012-e653-496a-9310-c1f4b6f8ac27
This commit is contained in:
parent
809af9708e
commit
e81632b4be
103 changed files with 2350 additions and 210 deletions
|
|
@ -11,18 +11,18 @@ application-area: [all]
|
|||
|
||||
## Description
|
||||
|
||||
An AppSource extension must carry a reserved affix — a prefix or a suffix of at least three characters — on the names of the objects it owns **and** on any field, key, control, or action it adds to a base-application object. The affix is registered with Microsoft; when two coexisting extensions would otherwise collide, the registrant of the affix wins. Without it, two apps that both add a `Loyalty Points` field to `Customer`, or both define a `Loyalty Tier` table, cannot be installed side by side.
|
||||
An AppSource extension must prevent name collisions through its registered affix or, on BC23 and later for objects it owns, a namespace with at least two levels. The affix still applies to every field, key, control, or action added to a base-application object; see `two-level-namespace-replaces-object-affix-not-extension-member-affix.md`. Without either mechanism, two apps that both define a `Loyalty Tier` table cannot coexist, and two apps that add an unaffixed `Loyalty Points` field to `Customer` still collide regardless of their namespaces.
|
||||
|
||||
AppSourceCop enforces this. The primary rule is AS0011 ("An affix is required"); the affixes are configured through `mandatoryAffixes` (and `mandatoryPrefix`) in `AppSourceCop.json`. Two placements matter and are easy to get half-right: an object you define carries the affix at **object-name** level, while a member you add to a **standard** object carries the affix on that **member's** name. Adding an affixed object is not enough — an unaffixed field bolted onto `Customer` still collides and still fails validation.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Own objects are named with the affix (e.g. a table `ABC Loyalty Tier`), and every field or action added to a standard object is individually affixed (e.g. `Loyalty Points ABC` on a `Customer` tableextension).
|
||||
Own objects use the registered affix (for example `ABC Loyalty Tier`) or, when targeting BC23 or later, a qualifying namespace. Every field or action added to a standard object remains individually affixed (for example `Loyalty Points ABC` on a `Customer` tableextension).
|
||||
|
||||
See sample: `object-affixes-prevent-collisions.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Unaffixed object or member names, or the common half-measure: the extension object carries the affix but a field it adds to a standard table does not. AS0011 flags the missing affix and the field can still collide with another app.
|
||||
An owned object with neither a qualifying namespace nor an affix, an unaffixed extension member, or the common half-measure where the extension object carries the affix but a field it adds to a standard table does not. AS0011 flags the missing collision protection and the field can still collide with another app.
|
||||
|
||||
See sample: `object-affixes-prevent-collisions.bad.al`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
table 50476 "Rental Setup Bad"
|
||||
{
|
||||
DataClassification = CustomerContent;
|
||||
|
||||
fields
|
||||
{
|
||||
field(1; "Primary Key"; Code[10]) { }
|
||||
}
|
||||
}
|
||||
|
||||
page 50477 "Rental Setup Bad"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = "Rental Setup Bad";
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
field("Primary Key"; Rec."Primary Key")
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Caption = 'Primary Key';
|
||||
ToolTip = 'Specifies the setup record.';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
codeunit 50478 "Rental Setup Mgt. Bad"
|
||||
{
|
||||
procedure Initialize()
|
||||
begin
|
||||
end;
|
||||
}
|
||||
|
||||
permissionset 50479 "Rental User"
|
||||
{
|
||||
Assignable = true;
|
||||
// The setup page opens, but saving or running setup logic requires SUPER.
|
||||
Permissions =
|
||||
page "Rental Setup Bad" = X;
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
table 50472 "Rental Setup"
|
||||
{
|
||||
DataClassification = CustomerContent;
|
||||
|
||||
fields
|
||||
{
|
||||
field(1; "Primary Key"; Code[10]) { }
|
||||
}
|
||||
}
|
||||
|
||||
page 50473 "Rental Setup"
|
||||
{
|
||||
PageType = Card;
|
||||
SourceTable = "Rental Setup";
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
field("Primary Key"; Rec."Primary Key")
|
||||
{
|
||||
ApplicationArea = All;
|
||||
Caption = 'Primary Key';
|
||||
ToolTip = 'Specifies the setup record.';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
codeunit 50474 "Rental Setup Mgt."
|
||||
{
|
||||
procedure Initialize()
|
||||
begin
|
||||
end;
|
||||
}
|
||||
|
||||
permissionset 50475 "Rental Manager"
|
||||
{
|
||||
Assignable = true;
|
||||
Permissions =
|
||||
tabledata "Rental Setup" = RIMD,
|
||||
table "Rental Setup" = X,
|
||||
page "Rental Setup" = X,
|
||||
codeunit "Rental Setup Mgt." = X;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: appsource
|
||||
keywords: [permission-set, super, appsource, setup, usage, tabledata, execute, submission]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# AppSource permission sets must cover setup and usage without SUPER
|
||||
|
||||
## Description
|
||||
|
||||
An AppSource app must provide permission sets that let assigned users complete the app's setup and normal usage without `SUPER`. The requirement is about complete effective grants, not about naming the permission set after the app. A package can compile and install with missing tabledata or execute permissions, then fail only when Marketplace validation or a real non-SUPER user reaches the omitted path.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Trace every setup page, normal page, report, codeunit, and tabledata operation exposed by the app and cover it through assignable role permission sets composed from focused non-assignable sets. Validate setup and representative workflows as a user assigned only those app roles. Grant the minimum required operations; completeness is not a reason to use wildcards.
|
||||
|
||||
See sample: `permission-sets-cover-setup-and-usage-without-super.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Shipping no permission set, omitting a tabledata or execute grant used by the app's own UI, or instructing users and validators to assign `SUPER` when setup fails. Do not flag a permission-set name that differs from the app name; no such naming requirement exists.
|
||||
|
||||
See sample: `permission-sets-cover-setup-and-usage-without-super.bad.al`.
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
namespace Contoso;
|
||||
|
||||
table 50462 "Rental Agreement"
|
||||
{
|
||||
DataClassification = CustomerContent;
|
||||
|
||||
fields
|
||||
{
|
||||
field(1; "No."; Code[20]) { }
|
||||
}
|
||||
}
|
||||
|
||||
tableextension 50463 "Rental Customer Ext" extends Customer
|
||||
{
|
||||
fields
|
||||
{
|
||||
field(50463; "Loyalty Points"; Integer)
|
||||
{
|
||||
DataClassification = CustomerContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
namespace Contoso.Rentals;
|
||||
|
||||
table 50460 "Rental Agreement"
|
||||
{
|
||||
DataClassification = CustomerContent;
|
||||
|
||||
fields
|
||||
{
|
||||
field(1; "No."; Code[20]) { }
|
||||
}
|
||||
}
|
||||
|
||||
tableextension 50461 "Rental Customer Ext" extends Customer
|
||||
{
|
||||
fields
|
||||
{
|
||||
field(50461; "Loyalty Points RNT"; Integer)
|
||||
{
|
||||
DataClassification = CustomerContent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
bc-version: [23..]
|
||||
domain: appsource
|
||||
keywords: [namespace, two-level, affix, prefix, suffix, as0011, tableextension, pageextension]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# A two-level namespace replaces an object affix, not an extension-member affix
|
||||
|
||||
## Description
|
||||
|
||||
Current AppSource naming guidance accepts a namespace with at least two levels, such as `Contoso.Rentals`, instead of a registered prefix or suffix on the names of objects the app owns. The namespace does not qualify members added to another publisher's object: fields, keys, controls, and actions introduced through table or page extensions still share the target object's flat member namespace and still need the registered affix.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Choose one collision strategy for owned objects: a registered affix or a globally meaningful namespace with at least two levels. Regardless of that choice, apply the registered affix to every member added to a base or third-party object. Keep the affix configured for AppSourceCop so member validation remains deterministic.
|
||||
|
||||
See sample: `two-level-namespace-replaces-object-affix-not-extension-member-affix.good.al`.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Using `namespace Contoso;` as though one level satisfied the AppSource alternative, or declaring `namespace Contoso.Rentals;` and then adding an unaffixed `Loyalty Points` field to `Customer`. The namespace distinguishes the extension's own objects; it cannot disambiguate members on Customer.
|
||||
|
||||
See sample: `two-level-namespace-replaces-object-affix-not-extension-member-affix.bad.al`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue