Fix lifecycle compatibility guidance (#93)

* Fix lifecycle compatibility guidance

Correct high-confidence Business Central guidance and samples for upgrade tags, collectible errors, trigger semantics, obsoletion, events, interfaces, API contracts, and test transactions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e05a43e7-6448-4d67-9c73-798523f5d945

* Address guidance review findings

Gate SecretText guidance to BC23 and clarify that the collectible-error sample intentionally emits a message-only blocking aggregate.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: e05a43e7-6448-4d67-9c73-798523f5d945

---------

Co-authored-by: Jesper Schulz-Wedde <jesper.schulzwedde@microsoft.com>
This commit is contained in:
Jesper Schulz-Wedde 2026-07-14 11:26:16 +02:00 committed by GitHub
parent aca3986fd0
commit 5706959e4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 201 additions and 104 deletions

View file

@ -1,12 +1,13 @@
// Malformed API endpoint: APIPublisher and APIGroup are missing, and there is
// no SourceTable. The page compiles but the route cannot be composed, so the
// entity is never published where an integration expects it.
// APIVersion is omitted. This is valid, but the endpoint defaults to beta
// instead of publishing the intended explicit stable contract.
page 50341 "WS Required Props Bad"
{
PageType = API;
APIVersion = 'v1.0';
APIPublisher = 'contoso';
APIGroup = 'sales';
EntityName = 'customer';
EntitySetName = 'customers';
SourceTable = Customer;
layout
{

View file

@ -7,20 +7,20 @@ countries: [w1]
application-area: [all]
---
# Declare every required property on a PageType = API page
# Declare API routing properties and an explicit stable version
## Description
An API page projects a table as an OData v4 / API v2 endpoint, but the platform only publishes that endpoint when the page carries the full set of identifying properties: `APIPublisher`, `APIGroup`, `APIVersion`, `EntityName`, `EntitySetName`, and a backing `SourceTable`. These properties are what compose the route — `/api/<publisher>/<group>/<version>/<entitySet>` — so omitting any one of them yields a page that compiles yet never surfaces as a usable endpoint, or surfaces at an unexpected address. An LLM that has mostly seen ordinary list/card pages tends to treat `PageType = API` as a cosmetic switch and forgets the identifying metadata, because a normal page needs none of it. This file is remedial precisely because the missing-property failure is silent: there is no runtime error, only an endpoint that clients cannot reach.
An API page needs `APIPublisher`, `APIGroup`, `EntityName`, `EntitySetName`, and a backing `SourceTable` to define its routed entity. `APIVersion` is different: it is optional at the language level and defaults to `beta`. Omitting it therefore does not mean the page has no version; it publishes under the preview contract. A production integration that intends a stable route should set a `vX.Y` version explicitly rather than rely on that default.
## Best Practice
On every `PageType = API` page set all six properties explicitly: `APIPublisher` (your publisher tag), `APIGroup` (the logical grouping for related entities), `APIVersion` (a `vX.Y` value such as `'v1.0'`), `EntityName` (singular), `EntitySetName` (plural), and `SourceTable` (the projected table). Expose the record's fields inside a single `field(...)` repeater under `area(content)`. Treat the six properties as a mandatory checklist that travels with the `PageType = API` declaration itself.
Declare the five routing/entity properties required by the API page and set `APIVersion` explicitly for a stable published contract, for example `'v1.0'`. Expose the record's fields inside a repeater under `area(content)`. Review missing routing metadata as a malformed API definition, but review a missing `APIVersion` as unintended publication under `beta`, not as an unpublished endpoint.
See sample: `set-required-api-page-properties.good.al`.
## Anti Pattern
Writing a page with `PageType = API` and a `SourceTable` but leaving out `APIPublisher` and `APIGroup` (and, worse, omitting `SourceTable` entirely). The page compiles, so it looks finished, but the endpoint is malformed: with no publisher and group the route cannot be composed, and the entity is never published where an integration expects it. The detection signal: a `PageType = API` page missing one or more of the six identifying properties.
Leaving out `APIPublisher`, `APIGroup`, `EntityName`, `EntitySetName`, or `SourceTable` leaves the API definition incomplete. A subtler contract defect is declaring all of those but omitting `APIVersion`: the page is exposed as `beta`, which is valid runtime behavior but not the explicit stable route a production client expects.
See sample: `set-required-api-page-properties.bad.al`.

View file

@ -1,13 +1,11 @@
// Additive versioning: v2.0 carries the new shape while v1.0 stays published and
// unchanged. APIVersion accepts a list, so both contracts are served and
// existing clients keep working while new clients adopt v2.0.
page 50354 "WS API Versioning Good"
// The original page remains the unchanged v1.0 contract.
page 50354 "Customer API v1"
{
PageType = API;
Caption = 'customer';
APIPublisher = 'contoso';
APIGroup = 'sales';
APIVersion = 'v2.0', 'v1.0';
APIVersion = 'v1.0';
EntityName = 'customer';
EntitySetName = 'customers';
ODataKeyFields = SystemId;
@ -37,3 +35,41 @@ page 50354 "WS API Versioning Good"
}
}
}
// A separate object carries the changed v2.0 shape.
page 50356 "Customer API v2"
{
PageType = API;
Caption = 'customer';
APIPublisher = 'contoso';
APIGroup = 'sales';
APIVersion = 'v2.0';
EntityName = 'customer';
EntitySetName = 'customers';
ODataKeyFields = SystemId;
SourceTable = Customer;
DelayedInsert = true;
layout
{
area(content)
{
repeater(records)
{
field(id; Rec.SystemId)
{
Caption = 'id';
Editable = false;
}
field(number; Rec."No.")
{
Caption = 'number';
}
field(legalName; Rec.Name)
{
Caption = 'legalName';
}
}
}
}
}

View file

@ -7,20 +7,20 @@ countries: [w1]
application-area: [all]
---
# Version APIs by adding a new APIVersion, not by mutating a published one
# Version changed API shapes with a new page object
## Description
Once an API version is published, external clients depend on its exact shape — the entity name, the set of exposed fields, the key — as a frozen contract. Changing any of that on the already-published version is a breaking change delivered silently: integrations that worked yesterday fail today with no warning. The platform gives you a clean way to evolve without breaking anyone, because `APIVersion` accepts a *list* of versions on one page. The correct way to change a published API is to add the new version (`'v2.0'`) alongside the existing one (`'v1.0'`) — or publish a new API page for it — so both contracts are served side by side and clients migrate on their own schedule. LLMs tend to "fix" an API by editing the live version in place, because in ordinary code you just change what's wrong; this file is remedial because a published API version is an immutable contract in a way ordinary internal code is not.
Once an API version is published, external clients depend on its exact shape — entity names, fields, keys, and behavior — as a stable contract. `APIVersion` can list several versions on one API page, but every listed route is generated from that same page object and therefore exposes the same shape. Adding `'v2.0'` to a page and then changing its fields changes what both `v1.0` and `v2.0` serve. To preserve the v1 shape while introducing a different v2 shape, keep the v1 page unchanged and create a separate page object for v2.
## Best Practice
When a published API must change shape, keep the old version's contract intact and add the new one to the `APIVersion` list — `APIVersion = 'v2.0', 'v1.0';`. The page now serves both `v1.0` (unchanged) and `v2.0` (carrying the new shape), so existing clients keep working while new clients adopt `v2.0`. Retire the old version only after consumers have migrated.
Keep the existing page object and its `APIVersion = 'v1.0'` contract unchanged. Copy the page to a new object ID, set that object's `APIVersion = 'v2.0'`, and make the v2-only shape changes there. A multi-value `APIVersion` list is appropriate only when the exact same page shape is supported under each listed version.
See sample: `version-apis-by-adding-not-mutating-published-versions.good.al`.
## Anti Pattern
Editing the published `v1.0` page in place — renaming its `EntityName` or removing an exposed field — so the single declared version now serves a different contract than the one clients integrated against. Every consumer of the old shape breaks without notice. The detection signal: a change that renames the entity or removes a field on an existing published `APIVersion` instead of adding a new version to the list.
Editing the published `v1.0` page in place breaks its clients. So does adding `v2.0` to that same page and assuming subsequent field changes apply only to v2: both routes use one object shape. The detection signal is a breaking shape change without a separate API page object retaining the old version.
See sample: `version-apis-by-adding-not-mutating-published-versions.bad.al`.