Skip to content

knowledge(web-services): under schema 2.0 an API enum field is a contract by member name, under 1.0 by caption - #149

Merged
Jesper Schulz-Wedde (JesperSchulz) merged 1 commit into
microsoft:mainfrom
waldo1001:community/web-services/api-enum-values-are-a-contract-by-name-not-ordinal
Sep 2, 2026
Merged

knowledge(web-services): under schema 2.0 an API enum field is a contract by member name, under 1.0 by caption#149
Jesper Schulz-Wedde (JesperSchulz) merged 1 commit into
microsoft:mainfrom
waldo1001:community/web-services/api-enum-values-are-a-contract-by-name-not-ordinal

Conversation

@waldo1001

@waldo1001 waldo (waldo1001) commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

What

Adds one web-services article with a good and a bad sample: community/knowledge/web-services/api-enum-values-are-a-contract-by-name-not-ordinal.md, .good.al, .bad.al.

The fact: what an API page publishes for an enum field depends on the OData $schemaversion the caller receives. Under schema 2.0 the field is a strongly typed enum and $metadata, responses and $filter carry the AL member names, with captions published separately through entityDefinitions. Under schema 1.0 the same field is Edm.String and responses carry the en-US caption. Microsoft's API v2.0 is always 2.0; custom APIs defaulted to 1.0 through BC 23, to 2.0 from BC 24, and a caller can still pin 1.0. The ordinal is never the carrier. Dataverse virtual tables match by External Name.

Why this is a knowledge file (admission test)

Three fresh cold reviewers, given a BC 22 custom API (schema 1.0 by default), each asserted one carrier as universal. The caption-only change was called "safe for API consumers, confidence high" because "API pages serialize enum fields by the enum member name, never by caption", which is wrong for the schema this app serves. The rename reviewer caught the schema 2.0 break but stated captions "only affect the UI". The knowledge probe put Edm.String in $metadata with the name in the payload, a mix of both schemas, with an invented version cutoff. The model does not know that the schema version decides, and is confident in both wrong directions. With the article, the caption-only diff on the BC 22 app is flagged with the correct mechanism and the reviewer states it would not have flagged it otherwise.

Overlap check

  • microsoft/knowledge/web-services/version-apis-by-adding-not-mutating-published-versions.mddelta: page-shape contract; a value or caption change inside an unchanged field changes none of it. Cross-referenced.
  • microsoft/knowledge/upgrade/enum-values-additive-at-end.mddelta: persisted rows bind by ordinal; across an API boundary the carrier is the name (2.0) or the caption (1.0). Cross-referenced.
  • No existing article mentions $schemaversion, entityDefinitions, External Name, virtual tables, or AS0082.
  • In flight: open upstream PRs searched by title for the top keywords; none states this fact.

Sources

bc-version: [17..]: API v2.0 and schema 2.0 exist from BC 17.

Layer and retrieval

Community layer. microsoft/skills/review/al-web-services-review.md already sources the web-services domain across layers, so no skill change is needed; review fixtures untouched. Happy to see it promoted if it proves itself.

Scope

Left out on purpose: ordinal stability for persisted rows and page-shape versioning, owned by the two cross-referenced Microsoft articles; Dataverse data synchronisation (option-set id mapping) is a different mechanism from virtual tables and is not claimed.

Review history

  • Round 1 (2026-09-02): opened with the caption/name rule stated as universal; the "false positive" cold probe in the evidence was a correct schema 1.0 answer.
  • Round 2 (2026-09-02, after the change request): title, Description, Best Practice, Anti Pattern and the detection guard scoped to schema 2.0, with schema 1.0 stated as the caption contract and the carve-out conditional on establishing 2.0; custom APIs that may be called as 1.0 preserve both contracts or version; bc-version moved to [17..]; good sample keeps names and captions and shows append plus ObsoleteState; cold probes re-run on a BC 22 app, warm reviews re-run; the withdrawn probe is marked as such in the Evidence block. Also: rebased on Avoid Public Event publisher #144, commit re-authored under a linked identity, ## See also and false-positive keyword added.

Evidence

# api-enum-values-are-a-contract-by-name-not-ordinal

Domain: web-services (review leaf: yes). Branch: community/web-services/api-enum-values-are-a-contract-by-name-not-ordinal, rebased on upstream/main 82422f9 (#144). Commit author 12088142+waldo1001@users.noreply.github.com (GitHub-linked).

Round 2 (2026-09-02, after JesperSchulz's change request): the article is now scoped to the OData schema version. Under 2.0 the member name is the carrier; under 1.0 the field is `Edm.String` and the caption is the carrier; custom APIs defaulted to 1.0 through BC 23 and to 2.0 from BC 24, and a caller can still pin 1.0. The round-1 "false positive" cold probe was a correct answer for schema 1.0 and is withdrawn as evidence.

in-flight: none — open upstream PRs searched by title for api-page, enum, enum-value-name (2026-09-02).
claim: under schema 2.0 enum properties are strongly typed; values from `$metadata`, captions from `entityDefinitions` — https://learn.microsoft.com/dynamics365/business-central/dev-itpro/api-reference/v2.0/transition-to-api-v2.0#enums (API v2.0 "is always set to 2.0")
claim: custom APIs: "Starting in version 24, the default value of $schemaversion is set to 2.0, also for custom APIs"; before that, 2.0 had to be requested explicitly — https://learn.microsoft.com/dynamics365/business-central/dev-itpro/upgrade/deprecated-features-platform#changes-in-2024-release-wave-1-version-240
claim: under schema 1.0 the enum property is `Edm.String` and the payload carries the en-US caption; under 2.0 the member name — maintainer-cited walkthrough https://www.kauffmann.nl/2024/08/22/custom-apis-and-schemaversion-2-0/ (CustomerLevel: 'Gold Level' on 1.0, 'GOLD' on 2.0); Learn states the 2.0 side and the default change, not the 1.0 payload shape explicitly
claim: API v2.0 introduced in 2020 release wave 2 (BC 17) — https://learn.microsoft.com/dynamics365/business-central/dev-itpro/upgrade/deprecated-features-w1#changes-in-2024-release-wave-1
claim: Dataverse virtual tables match enum values by External Name; integer values not stable — https://learn.microsoft.com/dynamics365/business-central/dev-itpro/powerplatform/powerplat-entity-modeling#table-fields
claim: AS0082 forbids renaming, AS0083 deleting an enum value; Upgrade-category rules that need a baseline — https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/analyzers/appsourcecop-as0082 and -as0083
claim (sample comments): bad = rename with ordinal and caption kept, schema 2.0 consumers break, AS0082 needs a baseline; good = names and captions kept, value appended, value obsoleted — both agree with the article.
bc-version: [17..] — API v2.0 and schema 2.0 start at BC 17 (maintainer: "The frontmatter should start at BC17").
precision: the legitimate shapes are a caption change where every consumer is on schema 2.0, and an appended value; both carved out explicitly. The carve-out is conditional on establishing schema 2.0; on a custom API where 1.0 is possible a caption change is reported, not suppressed.
context: the rule names its context (schema 2.0 vs 1.0, BC 17–23 default vs BC 24+, explicit `$schemaversion` pin) in the title and the first Description sentence.
overlap: microsoft/knowledge/web-services/version-apis-by-adding-not-mutating-published-versions.md — delta — page-shape contract and signal; a value or caption change inside an unchanged field leaves shape, entity, fields and keys identical.
overlap: microsoft/knowledge/upgrade/enum-values-additive-at-end.md — delta — persisted rows bind by ordinal; across an API boundary the carrier is the name (2.0) or the caption (1.0).
cold review (rerun, fresh reviewer, caption-only diff on a BC 22 custom API, app.json runtime 11.0): MISSED — "safe for API consumers ... API pages serialize enum fields by the enum member name, never by caption or ordinal. Confidence: high." Wrong for the schema 1.0 this app serves by default: the payload changes from 'Credit Note' to 'Credit Memo'.
cold review (rerun, fresh reviewer, rename diff, same app): caught the schema 2.0 break with the correct name mechanism, but stated "captions only affect the UI, and ordinals are never exposed on API pages" and did not know schema 1.0 consumers see no payload change. Half right, no schema awareness.
cold review (rerun, fresh reviewer, knowledge question, no files): MISSED — "$metadata: Edm.String ... JSON: the AL value name ... captions are not used on API pages", a mix of the 1.0 metadata and the 2.0 payload, with a made-up v15 cutoff. Confidence "high".
remedial for: the model does not know that the carrier depends on the OData schema version, and confidently asserts one carrier as universal in both directions.
warm review (bad sample): flagged citing community/knowledge/web-services/api-enum-values-are-a-contract-by-name-not-ordinal.md (one finding, rename at ordinal 2 on an API-exposed enum, mechanism and remedy from the article)
warm review (good sample): clean — names and captions kept, ReturnOrder appended, Quote obsoleted
warm review (caption-only diff, BC 22 custom API, consumer schema unknown): flagged citing the article — "custom API on platform 22 defaults to schema 1.0 ... consumer-visible contract change ... keep the caption or publish a new APIVersion"; reviewer states it would not have flagged this without the article.
script: 0 error(s), 0 warning(s) (2026-09-02, round 2, after rebase on 82422f9)
verdict: ready — round 2 addresses every point of the change request; remaining reviewer-side judgement is whether "another reliable context signal" is specific enough.

Checklist

  • Frontmatter has exactly the six required keys; domain matches the folder
  • ## Description present; no fenced code blocks; under 100 lines; one concern
  • Samples referenced by filename from the article and present next to it
  • validate_frontmatter.py, Test-KnowledgeIndex.ps1, Test-ReviewFixtures.ps1 pass locally
  • Only community/ is touched
  • Commit author is linked to the GitHub account; branch rebased on upstream/main

🤖 Generated with Claude Code

@waldo1001
waldo (waldo1001) force-pushed the community/web-services/api-enum-values-are-a-contract-by-name-not-ordinal branch from 44a5b78 to 07b1499 Compare September 2, 2026 13:36

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name-versus-caption rule needs to be scoped to the OData schema version before agents can use it safely.

For $schemaversion=2.0, the article is correct: enum fields are strongly typed and responses carry encoded AL member names, while captions are available separately through entityDefinitions. But schema version 1.0 exposes enum fields as Edm.String and returns the en-US caption. Custom APIs defaulted to 1.0 through BC23; BC24 changed only the default to 2.0, and callers can still explicitly request ?$schemaversion=1.0.

That reverses both blanket conclusions in the current text for schema 1.0:

  • changing a caption can break a consumer that receives that caption in responses;
  • renaming a member while retaining its caption does not change that schema-1.0 payload (although AS0082 and dependent-extension/upgrade contracts still prohibit the rename for separate reasons).

Please make the title, Description, Best Practice, Anti Pattern, and detection guard explicitly about schema version 2.0. Do not suppress caption-change findings unless the reviewer can establish schema 2.0 (standard API v2.0, an explicit $schemaversion=2.0, or another reliable context signal). For custom APIs where clients may select either schema, preserve both contracts or version the API. The frontmatter should start at BC17, when API/schema v2.0 was introduced, rather than BC16.

The rename risk under schema 2.0, Dataverse External Name mapping, and the AS0082/AS0083 discussion are otherwise well supported.

@JesperSchulz

Copy link
Copy Markdown
Contributor

One addition to my review above, because it affects the evidence rather than just the prose.

The PR body presents this as the article's proven remedial value:

cold review (neutral PR diff changing only the Caption of an exposed value): FALSE POSITIVE — reported as breaking API contract change: API pages serialize enum fields as strings derived from the value's caption, not its AL name. Wrong per Learn [...]

That cold reviewer was not wrong. Under \=1.0 an enum field is Edm.String and the payload carries the en-US caption; the name only appears under 2.0. Kauffmann's CustomerLevel walkthrough shows it plainly — value(3; GOLD) { Caption = 'Gold Level'; } serialises as Gold Level on schema 1.0 and GOLD on 2.0 (https://www.kauffmann.nl/2024/08/22/custom-apis-and-schemaversion-2-0/). Custom APIs defaulted to 1.0 through BC23, and a caller can still pin ?\=1.0 today.

So the cold reviewer described schema 1.0 correctly and the article marked it a false positive. That inverts the headline claim: the Do not flag a caption change guard would teach agents to suppress a genuine break on every custom API still served as 1.0.

This means the fix is not only in the wording. The two cold probes that argue for admission need re-running once the article is scoped to schema 2.0, since the caption probe currently counts a correct answer as a miss, and the rename probe was already caught cold when the diff was visible. Please restate what the article is remedial for after scoping — if the honest answer is the model does not know that the carrier depends on the schema version, that is a good article and a stronger admission case than the present one, but the Evidence block should say so.

To be clear about what I am not disputing: the rename risk under schema 2.0, the Dataverse External Name mapping, and the AS0082/AS0083 treatment are all well supported and should survive the rewrite intact.

…ract by member name, under 1.0 by caption

What an API page publishes for an enum field depends on the OData schema
version: member names under 2.0, the caption as Edm.String under 1.0, never
the ordinal. Custom APIs defaulted to 1.0 through BC 23 and to 2.0 from BC 24.
LLMs assert one carrier as universal and get one direction wrong.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@waldo1001
waldo (waldo1001) force-pushed the community/web-services/api-enum-values-are-a-contract-by-name-not-ordinal branch from 07b1499 to 39a5304 Compare September 2, 2026 14:20
@waldo1001 waldo (waldo1001) changed the title knowledge(web-services): enum values behind API page fields are a contract by name, not by ordinal knowledge(web-services): under schema 2.0 an API enum field is a contract by member name, under 1.0 by caption Sep 2, 2026
@waldo1001

Copy link
Copy Markdown
Contributor Author

Thanks, you were right on every point, and the caption probe was indeed a correct schema 1.0 answer rather than a false positive.

Round 2 is pushed (39a5304):

  • Title, Description, Best Practice, Anti Pattern and the detection guard are scoped to schema 2.0. Schema 1.0 is stated as the caption contract (Edm.String, en-US caption), with the BC 17–23 default, the BC 24 default change, and the explicit $schemaversion=1.0 pin named in the first paragraph.
  • The caption carve-out now applies only where schema 2.0 can be established for every consumer (Microsoft API v2.0, an explicit $schemaversion=2.0, or another reliable context signal). On a custom API where clients may still select 1.0, a caption change is reported as a consumer-visible change with versioning as the remedy. Custom APIs that may be called as 1.0 preserve both contracts or publish a new APIVersion.
  • bc-version is [17..].
  • The good sample keeps both name and caption and shows append plus ObsoleteState instead of the caption edit.

The cold probes were re-run with fresh reviewers on a BC 22 custom API (runtime 11.0, schema 1.0 by default). The caption-only change was called "safe for API consumers, confidence high" on the belief that "API pages serialize enum fields by the enum member name, never by caption". The rename probe caught the 2.0 break but stated captions "only affect the UI". A no-files knowledge probe put Edm.String in $metadata with the name in the payload. So the honest remedial claim is the one you suggested: the model does not know that the carrier depends on the schema version, and asserts one carrier as universal in both directions. The Evidence block in the PR body says so and marks the round-1 probe as withdrawn. With the article, the same caption-only diff is flagged with the schema 1.0 mechanism, and the warm reviewer states it would not have flagged it otherwise.

Rename risk under 2.0, the Dataverse External Name mapping and the AS0082/AS0083 treatment are unchanged.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2 addresses everything, and the withdrawal is handled honestly rather than quietly.

The carrier is now conditional on the schema version in the title, Description, Best Practice, Anti Pattern and the detection guard, and the caption carve-out is gated on the reviewer establishing schema 2.0 rather than assumed. The custom-API case — preserve both contracts or version — is the right instruction for the many apps that can still be called either way. bc-version: [17..] matches API v2.0's introduction in 2020 wave 2.

The Evidence rework is the part I care most about: the round-1 false positive is marked withdrawn, and the re-run probes on a BC 22 app are much stronger than what they replace. Three fresh reviewers each asserting a different universal carrier — one of them inventing a v15 cutoff, all at high confidence — is a better admission case than the original, and it is the case the article actually answers.

On the open question in your verdict: another reliable context signal is loose, but I would rather leave it loose than enumerate signals we would have to maintain. The guard is fail-safe as written — ambiguity leads to reporting, not to suppressing — so the vague phrase cannot cause a silent miss. Leaving it.

Thanks for taking the correction on the evidence rather than only the prose.

@JesperSchulz
Jesper Schulz-Wedde (JesperSchulz) merged commit 2439d5c into microsoft:main Sep 2, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants