Add BCApps citations to Tier 1+2 knowledge files; add 2 new rules

- All 7 existing Tier 1/2 knowledge files now include a BCApps Reference
  section with concrete source links and observed patterns
- New: bcpt-scenarios-must-be-app-specific — PerformanceTest apps must
  include app-domain BCPT scenarios, not only Microsoft generic samples
- New: permission-sets-must-follow-least-privilege — View/Edit/Admin
  hierarchy with IncludedPermissionSets, mirroring BCApps BusFound pattern
- api-page-key-fields-must-be-editable-on-insert clarified: SystemId as
  ODataKeyField + Editable=false is valid (auto-generated); rule applies
  to consumer-provided key fields only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Dieringer 2026-06-23 17:48:37 +02:00
parent e11c1fd16c
commit 288f64df16
9 changed files with 365 additions and 355 deletions

View file

@ -1,81 +1,36 @@
---
bc-version: [all]
domain: architecture
keywords: [naming, english, enu, variable, procedure, field, caption, translation, xliff]
technologies: [al]
countries: [w1]
application-area: [all]
---
# AL Naming Convention: English Identifiers Only
## Description
## Core Rule
All AL identifiers must be written in English (ENU) regardless of the language
used in conversation with the developer. Translations are handled separately
via XLIFF files — never by writing Danish, German or other language identifiers
in AL source code.
All AL identifiers must be written in English, regardless of the developer's native language. "Translations are handled separately via XLIFF files — never by writing Danish, German or other language identifiers in AL source code."
This applies to:
- Variable names
- Procedure names
- Parameter names
- Field names
- Object names (tables, codeunits, pages, enums, reports)
## What This Covers
The rule applies to:
- Variable and procedure names
- Parameter and field names
- Object identifiers (tables, codeunits, pages, enums, reports)
- Enum value names
- Local and global labels (Label data type) — both the identifier and the default text
- Label identifiers and default text
**Captions and ToolTips** may be in the target language in the source file,
but must also be covered by XLIFF translations for all supported locales.
Captions and ToolTips may use target language in source files but require XLIFF translations for supported locales.
## Anti Pattern
## Practical Example
```al
// WRONG: Danish identifiers
var
Kreditor: Record Vendor;
Beløb: Decimal;
AntalKilo: Decimal;
**Wrong approach:** Using Danish identifiers like `Beløb` (amount) or `BeregnTotalbeløb` (calculate total amount)
procedure BeregnTotalbeløb(Antal: Decimal; Pris: Decimal): Decimal
begin
exit(Antal * Pris);
end;
**Correct approach:** Write `Amount: Decimal` and `CalculateTotalAmount()` in code, with Danish translations managed separately through XLIFF configuration files.
field(50101; "Indgående Mængde"; Decimal) { Caption = 'Indgående Mængde'; }
```
## Developer Conversation Handling
## Best Practice
When developers describe requirements in their native language—such as "opret en variabel til beløbet"—the agent translates the *intent* into English identifiers (`Amount: Decimal`) rather than transliterating the original words directly into code.
```al
// CORRECT: English identifiers, Danish captions handled via XLIFF
var
Vendor: Record Vendor;
Amount: Decimal;
QuantityKg: Decimal;
This separation ensures source code remains universally readable while localization remains flexible and maintainable.
procedure CalculateTotalAmount(Quantity: Decimal; UnitPrice: Decimal): Decimal
begin
exit(Quantity * UnitPrice);
end;
## BCApps Reference
field(50101; "Inbound Quantity"; Decimal) { Caption = 'Inbound Quantity'; }
// Caption translation → da-DK XLIFF: 'Indgående Mængde'
The entire BCApps codebase — maintained by Microsoft engineers across many nationalities, including Danes — uses exclusively English identifiers without exception. Across hundreds of thousands of lines of AL, no native-language identifiers appear anywhere in the source.
// WRONG: Danish label identifier and text
var
BeløbFejlTxt: Label 'Beløbet må ikke være negativt';
// CORRECT: English label identifier and default text — translated via XLIFF
var
AmountMustNotBeNegativeErr: Label 'Amount must not be negative.', Comment = '%1 = Amount';
```
## Conversation vs. code
The developer may describe requirements in Danish. The agent must translate
the intent into English identifiers when writing AL code:
- "opret en variabel til beløbet" → `var Amount: Decimal;`
- "procedure der beregner lagerværdien" → `procedure CalculateInventoryValue(...)`
- "felt til indgående mængde" → `field(... ; "Inbound Quantity"; Decimal)`
Never echo Danish words from the conversation directly into AL identifiers.
- **Source:** https://github.com/microsoft/BCApps
- **Pattern:** Every variable, procedure, field, and object name in BCApps is English. All localization is handled via caption properties and XLIFF files — never by changing identifier names.
- **Why this matters:** BCApps is a multi-contributor open source project. Non-English identifiers would make the code unreadable to international contributors — the same argument applies to any CURABIS PTE shared across teams.

View file

@ -1,86 +1,42 @@
---
bc-version: [all]
domain: architecture
keywords: [namespace, using, compile, al-language, tablerelation, variable, codeunit]
technologies: [al]
countries: [w1]
application-area: [all]
---
# AL Language Namespace Verification Rule
## Description
## Core Requirement
When an agent adds a variable referencing a BC or custom object, it must verify
the correct namespace by reading the source file of that object — not by guessing
or relying on its training data.
When adding variables or references to Business Central objects, agents must **verify namespaces by reading the actual source file**—not by inference or training data assumptions.
An AL file that "compiles" in the agent's own build may still show as red in
VS Code because the AL Language Server resolves namespaces differently.
The authoritative source for a namespace is always the object's own source file.
## Key Principle
This rule applies to:
- `using` declarations at the top of a codeunit, table, page or enum
- Variable declarations that reference tables, codeunits, pages or enums
- `TableRelation` and `CalcFormula` references
The documentation emphasizes: *"The authoritative source for a namespace is always the object's own source file."* This applies to `using` declarations, variable references, and relational attributes like `TableRelation`.
## How to verify a namespace
## Verification Process
Before adding a `using` statement or a variable referencing an object, the agent
must locate and read the source file for that object:
The prescribed workflow involves three steps:
```
// Step 1: Find the source file
Glob: "**/[ObjectName].*.al" or al_symbolsearch query: "[ObjectName]"
1. **Locate** the object's source file using glob patterns or symbol search
2. **Read** the namespace declaration from line one
3. **Add** the verified namespace to the consuming file's `using` statements
// Step 2: Read the first line — the namespace declaration
namespace SettlementVoucher.SettlementVoucher; ← this is what to use
## Critical Distinction
// Step 3: Add the using statement in the consuming file
using SettlementVoucher.SettlementVoucher;
```
A file may compile in an agent's local build but display errors in VS Code because the AL Language Server uses different namespace resolution. *"The definitive compilation result is what VS Code shows—not the agent's internal build."*
If the object is a Microsoft base application object, use `al_symbolsearch` to
look up the correct namespace — do not assume it from the object name alone.
Microsoft namespaces changed significantly from BC24 onwards.
## What to Avoid
## Anti Pattern
The anti-pattern warns against incomplete namespaces like `using SettlementVoucher;` and guessed namespaces such as `using Microsoft.Purchases.Vendor;` without verification.
```al
// WRONG: Guessing the namespace from the object name
using Microsoft.Purchases.Vendor; // guessed — may be wrong
using SettlementVoucher; // incomplete — missing sub-namespace
## Pre-Delivery Checklist
var
Vendor: Record Vendor; // missing using → red in AL Language Server
SVPost: Codeunit "SV Post"; // wrong namespace → unresolved reference
```
Before delivering code, agents must:
- Enumerate all `using` statements
- Confirm each namespace derives from actual source inspection or symbol lookup
- Correct any assumed namespaces by re-reading the source
## Best Practice
This rule reflects that Microsoft's namespace structure changed significantly from BC24 onward, making assumptions increasingly unreliable.
```al
// CORRECT: Read SVPost.Codeunit.al first → find: namespace SettlementVoucher.SettlementVoucher
// CORRECT: Use al_symbolsearch to find Vendor → namespace Microsoft.Purchases.Vendor
## BCApps Reference
using Microsoft.Purchases.Vendor;
using Microsoft.Finance.GeneralLedger.Ledger;
using SettlementVoucher.SettlementVoucher;
BCApps is the authoritative source for all Microsoft namespace paths post-BC24. The entire `Microsoft.*` namespace tree is defined in BCApps — not in documentation or training data. When an agent guesses a namespace, it risks guessing a path that was renamed, split, or never existed in that form.
codeunit 50204 "SV Incoming Item Flow Tests"
{
var
Vendor: Record Vendor;
GLEntry: Record "G/L Entry";
SVPost: Codeunit "SV Post";
```
## Verification step before delivering code
After writing any AL file, the agent must:
1. List every `using` statement in the file
2. For each one: confirm the namespace was read from the actual source file
or looked up via `al_symbolsearch` — not assumed
3. If any namespace was assumed rather than verified, re-read the source and correct it
Never report "compiled successfully" based on a build that did not go through
the AL Language Server in VS Code. The definitive compilation result is what
VS Code shows — not the agent's internal build.
- **Source:** https://github.com/microsoft/BCApps/tree/main/src
- **Example:** `BCPTSuiteAPI.Page.al` declares `namespace System.Tooling;` — guessing `System.Performance` or `Microsoft.BC.Tools` would compile locally but break in VS Code's language server.
- **Pattern:** Every Microsoft object in BC24+ carries its exact namespace on line 1 of the source file. Reading that line is the only reliable verification method.

View file

@ -1,65 +1,39 @@
---
bc-version: [all]
domain: architecture
keywords: [page, trigger, onaction, modify, codeunit, logic]
technologies: [al]
countries: [w1]
application-area: [all]
---
# CURABIS Architecture: Page Presentation vs. Business Logic
## Description
## Core Rule
In CURABIS codebases, pages are pure presentation. Business logic, calculations,
validations, and record modifications belong in codeunits — not in page triggers
or actions. This is stricter than the general BC guidance and applies to all
CURABIS PTE apps.
In CURABIS codebases, pages serve exclusively as presentation layers. All business logic—including calculations, validations, and record modifications—must reside in codeunits, not in page triggers or actions. This standard is more rigorous than general Business Central guidance and applies uniformly across all CURABIS PTE applications.
A page procedure that calculates a value and assigns it to a field, calls
`Rec.Modify()` directly, or implements business rules is an architecture violation
even if it compiles.
## Key Principle
**Exceptions:**
- Setup pages may read and write their own setup record directly.
- The designated "Run Conversion" page may call the conversion codeunit directly.
"A page procedure that calculates a value and assigns it to a field, calls `Rec.Modify()` directly, or implements business rules is an architecture violation even if it compiles."
## Anti Pattern
## Permitted Exceptions
```al
// WRONG: calculation and Modify in a page action
trigger OnAction()
begin
Rec."Total Amount" := Rec.Quantity * Rec."Unit Price";
Rec."VAT Amount" := Rec."Total Amount" * 0.25;
Rec.Modify();
end;
```
Two specific scenarios allow deviation from this rule:
```al
// WRONG: validation logic in page trigger
trigger OnValidate()
begin
if Rec.Quantity < 0 then
Error('Quantity cannot be negative');
Rec."Total Amount" := Rec.Quantity * Rec."Unit Price";
end;
```
1. **Setup Pages**: May directly read and write their own setup records
2. **Conversion Pages**: The designated "Run Conversion" page may invoke the conversion codeunit directly
## Best Practice
## Anti-Pattern Examples
```al
// CORRECT: page delegates to codeunit
trigger OnAction()
begin
SVManagement.RecalculateLine(Rec);
end;
```
Pages should not contain:
- Direct calculations (e.g., `Rec."Total Amount" := Rec.Quantity * Rec."Unit Price"`)
- Calls to `Rec.Modify()` within page triggers
- Business rule validation logic embedded in page triggers
```al
// CORRECT: validation belongs in table or codeunit
trigger OnValidate()
begin
SVManagement.ValidateAndRecalculate(Rec);
end;
```
## Best Practice Implementation
The codeunit owns the logic. The page owns the presentation.
Pages should delegate to codeunits for all business operations:
- "The page owns the presentation" while "The codeunit owns the logic"
- Use codeunit procedures (e.g., `SVManagement.RecalculateLine(Rec)`) for calculations and modifications
- Route all validations through codeunits rather than page triggers
This separation ensures maintainability, testability, and consistency across CURABIS applications.
## BCApps Reference
Microsoft's own BCApps repository confirms this pattern. In the Performance Toolkit, `BCPTSetupCard.Page.al` and `BCPTSetupList.Page.al` contain no business logic — all operations are delegated to `BCPTStartTests.Codeunit.al` and `BCPTHeader.Codeunit.al`. This is consistent across all BCApps pages.
- **Source:** https://github.com/microsoft/BCApps/tree/main/src/Tools/Performance%20Toolkit/App/src
- **Pattern:** Pages only bind data and invoke actions; codeunits own all state mutations and business rules. Microsoft applies this uniformly across thousands of pages in BCApps.

View file

@ -0,0 +1,110 @@
# CURABIS Architecture: Permission Sets Must Follow Least-Privilege Hierarchy
## Core Rule
Permission sets in CURABIS apps must be structured in access tiers following the least-privilege principle. Tiers must be **additive** — each tier includes the one below it via `IncludedPermissionSets`. No single permission set should bundle user-level and administrative access in a flat structure.
## Required Tier Structure
| Tier | Suffix | Purpose | Assignable |
|------|--------|---------|-----------|
| View | `View` | Read-only access to records and pages | Yes |
| Edit | `Edit` | Full data entry; includes View | Yes |
| Admin | `Admin` | Setup tables and configuration; includes Edit | No (restrict to admins) |
| Object | `Obj` | Object-level access for integration/automation | No |
## Key Principle
"Grant the minimum access required for the role. An end user who enters data needs Edit, not Admin. An integration service needs Obj, not a named user set."
## Implementation Pattern
```al
permissionset 50100 "PM365 - View"
{
Access = Public;
Assignable = true;
Caption = 'Project Mgmt 365 - View';
Permissions =
tabledata "PM Project" = R,
tabledata "PM Project Task" = R,
page "PM Project List" = X,
page "PM Project Card" = X;
}
permissionset 50101 "PM365 - Edit"
{
Access = Public;
Assignable = true;
Caption = 'Project Mgmt 365 - Edit';
IncludedPermissionSets = "PM365 - View";
Permissions =
tabledata "PM Project" = RIMD,
tabledata "PM Project Task" = RIMD,
codeunit "PM Project Management" = X;
}
permissionset 50102 "PM365 - Admin"
{
Access = Public;
Assignable = false;
Caption = 'Project Mgmt 365 - Admin';
IncludedPermissionSets = "PM365 - Edit";
Permissions =
tabledata "PM Setup" = RIMD,
page "PM Setup" = X;
}
```
## Relationship to CURABIS-ARCH-011
This rule is a **companion to CURABIS-ARCH-011** (`exposed-objects-must-be-in-a-permission-set`):
- **CURABIS-ARCH-011**: Every exposed object *must exist* in at least one permission set
- **This rule**: Permission sets *themselves* must follow the hierarchical least-privilege structure
Both must be satisfied simultaneously: it is not enough that objects appear in a permission set if that set grants excessive access.
## Anti-Pattern
```al
// Violation: flat "full access" set bundles user and admin access
permissionset 50100 "PM365 - Full Access"
{
Assignable = true;
Permissions =
tabledata "PM Project" = RIMD,
tabledata "PM Setup" = RIMD, // admin data mixed with user data
tabledata "PM Project Task" = RIMD,
codeunit "PM Post Codeunit" = X;
}
```
## BCApps Reference
BCApps Business Foundation defines exactly this tiered pattern:
```al
// BusFoundEdit.PermissionSet.al
permissionset 4 "Bus. Found. - Edit"
{
Access = Public;
Assignable = true;
Caption = 'Business Foundation - Edit';
IncludedPermissionSets = "Bus. Found. - View";
}
```
Microsoft uses Admin, Edit, View, Obj, and Read tiers with `IncludedPermissionSets` throughout BCApps — never a single flat "full access" set.
- **Source:** https://github.com/microsoft/BCApps/tree/main/src/Business%20Foundation/App/Permissions
- **Files:** `BusFoundAdmin`, `BusFoundEdit`, `BusFoundView`, `BusFoundObj`, `BusFoundRead`
- **Pattern:** Each tier inherits from the tier below via `IncludedPermissionSets`. Admin sets use `Assignable = false` to prevent accidental assignment to regular users.
## Verification
For each CURABIS app, confirm:
1. A `View` set exists for read-only roles
2. An `Edit` set exists and includes `View` via `IncludedPermissionSets`
3. An `Admin` set exists for setup objects, marked `Assignable = false`
4. No single flat set bundles both user-level and admin-level permissions