Improve partner onboarding and documentation navigation

Lead with a complete plugin quick start and add task-oriented usage, troubleshooting, customization, and contribution guides. Preserve the broader plugin framing, correct conflicting contract guidance, support Agents folder reviews, and align repository validation. Convert existing sample references to clickable links without changing knowledge rules.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Jesper Schulz-Wedde 2026-09-09 17:25:29 +02:00
parent a21edfec46
commit b6da405376
276 changed files with 1287 additions and 756 deletions

View file

@ -17,10 +17,10 @@ Access is a decision about what you are willing to support forever. The moment a
Start everything `local` or `internal` and promote a member to `public` only when you have decided to support it as a stable contract. Expose a small, intentional surface — the supported entry point — and keep validation, posting, and helper routines `internal` for in-app reuse or `local` when single-object. Do not drop `[Scope('OnPrem')]` without intent, since that too widens the contract. Every public member is a maintenance commitment; spend them deliberately.
See sample: `choose-access-modifiers-deliberately.good.al`.
See sample: [`choose-access-modifiers-deliberately.good.al`](choose-access-modifiers-deliberately.good.al).
## Anti Pattern
Declaring every procedure `public` by default, so internal helpers like `ValidateOrder` and `PostOrder` become a de-facto API that consumers bind to and that can no longer be changed freely. Detection: an object where implementation-detail procedures carry no access modifier or are `public` without a reason to support them externally. Default them to `internal`/`local` and make only the intended entry point public.
See sample: `choose-access-modifiers-deliberately.bad.al`.
See sample: [`choose-access-modifiers-deliberately.bad.al`](choose-access-modifiers-deliberately.bad.al).

View file

@ -17,10 +17,10 @@ Deleting or renaming a published procedure (or object) in a single release is a
When a published procedure is superseded, keep it in place and mark it `[Obsolete('Use CalculateNetAmount instead.', '25.0')]`, where the message names the replacement and the tag records when the method became obsolete. Have the obsolete member forward to the new one so behavior is preserved during the window. Only after the deprecation window has elapsed should a later release delete the method. For an object or field, use `Pending` during the warning window and `Removed` afterward.
See sample: `deprecate-public-members-with-the-obsolete-lifecycle.good.al`.
See sample: [`deprecate-public-members-with-the-obsolete-lifecycle.good.al`](deprecate-public-members-with-the-obsolete-lifecycle.good.al).
## Anti Pattern
Renaming or deleting the published `CalcNet` procedure in place — replacing it with `CalculateNetAmount` and nothing else — so consumers calling `CalcNet` break immediately with no deprecation notice. Detection: a previously shipped non-`local` procedure that vanished or was renamed between versions with no `[Obsolete]` marker left behind during a prior warning window. Do not suggest `ObsoleteState = Removed` for a method; that property belongs to supported object and element types.
See sample: `deprecate-public-members-with-the-obsolete-lifecycle.bad.al`.
See sample: [`deprecate-public-members-with-the-obsolete-lifecycle.bad.al`](deprecate-public-members-with-the-obsolete-lifecycle.bad.al).

View file

@ -19,10 +19,10 @@ This rule governs procedures that dependents *call*. An event publisher — a pr
Treat a published signature as frozen. When new behavior needs more inputs, add a new procedure or overload alongside the original — for example a `CalculateDiscountWithRate(Amount; Rate)` next to the unchanged `CalculateDiscount(Amount)` — and let the old one delegate to the new one. Existing callers keep compiling; new callers opt into the richer entry point. Naming an unnamed return value is the one in-place change that is always safe.
See sample: `do-not-change-published-procedure-signatures.good.al`.
See sample: [`do-not-change-published-procedure-signatures.good.al`](do-not-change-published-procedure-signatures.good.al).
## Anti Pattern
Editing the existing public procedure's parameter list — here, adding a `Rate` parameter to `CalculateDiscount` — so every dependent extension that called the old form fails to compile. Detection: a parameter added, removed, reordered, retyped, or flipped to/from `var`, or a changed return type, on any non-`local` procedure that already shipped. Add a new overload instead. Exclude event publishers whose only change is an added parameter: subscribers bind by parameter name, not position, so that edit is additive and reporting it here is a false positive.
See sample: `do-not-change-published-procedure-signatures.bad.al`.
See sample: [`do-not-change-published-procedure-signatures.bad.al`](do-not-change-published-procedure-signatures.bad.al).

View file

@ -17,10 +17,10 @@ Every member you make publicly reachable becomes a contract you must keep — an
Keep secrets in `internal` or `local` members, and prefer the `SecretText` type so the value cannot be read back or logged. Where callers genuinely need a credential, pass it inward (a setter) rather than handing it outward (a getter). Public API should return only non-sensitive data — a masked reference, a status, a business identifier — never the raw secret. Treat each public member as a lasting commitment and keep the security-sensitive surface as small as possible.
See sample: `do-not-expose-sensitive-data-through-public-api.good.al`.
See sample: [`do-not-expose-sensitive-data-through-public-api.good.al`](do-not-expose-sensitive-data-through-public-api.good.al).
## Anti Pattern
A public `GetAccessToken()` that returns the raw token (or an event parameter carrying a credential to all subscribers), turning a secret into a de-facto public API any dependent can consume. Detection: a non-`local` procedure, event parameter, or global variable that surfaces a token, password, key, or other credential. Keep the secret internal and expose only non-sensitive data.
See sample: `do-not-expose-sensitive-data-through-public-api.bad.al`.
See sample: [`do-not-expose-sensitive-data-through-public-api.bad.al`](do-not-expose-sensitive-data-through-public-api.bad.al).

View file

@ -17,10 +17,10 @@ A member carrying `[Obsolete]`, or wrapped in a `#if not CLEANxx` conditional-co
Leave obsolete members exactly as they are and implement against the current, supported replacement. New logic — a surcharge calculation, an event publisher, a hook — belongs on the live API (`GetUnitPrice`), never inside the deprecated `GetPrice` or behind a `#if not CLEAN25` guard. If the replacement does not yet exist, create it as a first-class member and build there. The obsolete code should only shrink over time, not accrete new behavior.
See sample: `do-not-modify-code-already-marked-obsolete.good.al`.
See sample: [`do-not-modify-code-already-marked-obsolete.good.al`](do-not-modify-code-already-marked-obsolete.good.al).
## Anti Pattern
Adding a surcharge calculation inside the `[Obsolete]` `GetPrice` procedure, or behind a `#if not CLEAN25` block, so the new behavior is wired to code that will be removed when `CLEAN25` is enabled. Detection: new statements, event declarations, or dependencies introduced inside an `[Obsolete]`-marked member or a `#if not CLEANxx` region. Move the logic onto the supported replacement instead.
See sample: `do-not-modify-code-already-marked-obsolete.bad.al`.
See sample: [`do-not-modify-code-already-marked-obsolete.bad.al`](do-not-modify-code-already-marked-obsolete.bad.al).

View file

@ -17,10 +17,10 @@ AL resolves an object by namespace and name. Once an app ships and dependent ext
Choose a globally meaningful namespace before first publication and keep it stable. Add new functional areas beneath that structure without moving existing published objects. If an identity must move, use the platform's supported move/obsoletion lifecycle rather than a source-only namespace rename.
See sample: `namespace-is-part-of-published-object-identity.good.al`.
See sample: [`namespace-is-part-of-published-object-identity.good.al`](namespace-is-part-of-published-object-identity.good.al).
## Anti Pattern
Changing `namespace Contoso.Rentals;` to `namespace Contoso.RentalManagement;` as a cleanup while leaving the object name and ID untouched. Every dependent `using` directive and qualified reference targets the old identity and stops compiling.
See sample: `namespace-is-part-of-published-object-identity.bad.al`.
See sample: [`namespace-is-part-of-published-object-identity.bad.al`](namespace-is-part-of-published-object-identity.bad.al).

View file

@ -17,10 +17,10 @@ A shipped table field carries both a source-level contract and persisted data. R
Keep the old field's ID, name, and type unchanged. Add the replacement as a separate field under an unused ID, then mark the old field `ObsoleteState = Pending` with an `ObsoleteReason` that names the replacement and an `ObsoleteTag` recording the obsoletion version. Keep the old field readable so an upgrade codeunit can copy its data during the deprecation window. Move it to `ObsoleteState = Removed` only in a later release, after the window has passed and data has migrated.
See sample: `obsolete-table-fields-instead-of-deleting-them.good.al`.
See sample: [`obsolete-table-fields-instead-of-deleting-them.good.al`](obsolete-table-fields-instead-of-deleting-them.good.al).
## Anti Pattern
Renaming published `Email` to `Contact Email` with the same ID violates the compatibility contract and AS0005, even though the retained ID does not itself imply a fresh empty column. Deleting `Email` or changing its ID additionally risks losing its stored values. Detection: any previously shipped field whose name changes at the same ID, or whose original ID disappears without the unchanged field being retained as `Pending` and its data migrated to a separate replacement field.
See sample: `obsolete-table-fields-instead-of-deleting-them.bad.al`.
See sample: [`obsolete-table-fields-instead-of-deleting-them.bad.al`](obsolete-table-fields-instead-of-deleting-them.bad.al).