From 62dabf9a112e2400e9625c20b107b1d5a0de9d3b Mon Sep 17 00:00:00 2001 From: Jesper Schulz-Wedde Date: Fri, 17 Apr 2026 13:45:33 +0200 Subject: [PATCH] Co-locate AL samples next to their knowledge articles The /samples/ top-level tree is replaced with sibling files in each knowledge-layer folder. An article and its demonstrations now live side-by-side: microsoft/knowledge//.md microsoft/knowledge//.good.al microsoft/knowledge//.bad.al Rationale: - Proximity. An article and its paired samples are one unit; the filesystem now reflects that. - Layer ownership. Samples inherit layer precedence for free -- a /custom/ fork can override an article and its samples atomically, which the shared /samples/ tree previously made awkward. - Trivial migration path. Action-skill source globs (*/knowledge//**/*.md) are unchanged; sample discovery is a sibling-filename lookup. Changes: - git mv of all 65 sample files from samples///{bad,good}.al to microsoft/knowledge//.{bad,good}.al (history preserved). - Update See-sample references in all 37 articles that ship samples. - skills/read.md: replace the no-code-blocks bullet with a pointer to a new Sample files section that fully specifies the sibling convention, the kinds (good/bad + forward-compatible), multi-technology rules, demonstration-only status, and layer-precedence behaviour. - skills/write.md: update the samples pointer to match. - README.md: annotate the knowledge tree with the sample sibling shape. - samples/README.md deleted; content lifted into skills/read.md. - Both generators (C:\temp\gen_performance_knowledge.py, C:\temp\gen_security_knowledge.py) updated to emit at the new paths and to stop writing samples/README.md. Re-running them is idempotent against the committed layout. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 4 +-- .../add-sift-keys-for-flowfields.good.al | 0 .../add-sift-keys-for-flowfields.md | 2 +- .../avoid-calcfields-in-loops.bad.al | 0 .../avoid-calcfields-in-loops.good.al | 0 .../performance/avoid-calcfields-in-loops.md | 4 +-- .../avoid-commit-inside-loops.bad.al | 0 .../performance/avoid-commit-inside-loops.md | 2 +- .../avoid-findfirst-with-next.bad.al | 0 .../performance/avoid-findfirst-with-next.md | 2 +- ...id-user-interaction-in-transactions.bad.al | 0 ...d-user-interaction-in-transactions.good.al | 0 .../avoid-user-interaction-in-transactions.md | 4 +-- .../performance/filter-before-find.bad.al | 0 .../performance/filter-before-find.good.al | 0 .../performance/filter-before-find.md | 4 +-- .../keep-event-subscribers-lightweight.bad.al | 0 .../keep-event-subscribers-lightweight.md | 2 +- .../keep-transaction-scope-short.bad.al | 0 .../keep-transaction-scope-short.good.al | 0 .../keep-transaction-scope-short.md | 4 +-- .../only-fetch-records-you-use.bad.al | 0 .../only-fetch-records-you-use.good.al | 0 .../performance/only-fetch-records-you-use.md | 4 +-- ...prefer-direct-record-over-recordref.bad.al | 0 ...refer-direct-record-over-recordref.good.al | 0 .../prefer-direct-record-over-recordref.md | 4 +-- .../prefer-get-for-primary-key-lookups.bad.al | 0 ...prefer-get-for-primary-key-lookups.good.al | 0 .../prefer-get-for-primary-key-lookups.md | 4 +-- .../set-current-key-to-match-filters.good.al | 0 .../set-current-key-to-match-filters.md | 2 +- ...se-addloadfields-in-report-layouts.good.al | 0 .../use-addloadfields-in-report-layouts.md | 2 +- .../use-calcsums-for-flowfield-totals.bad.al | 0 .../use-calcsums-for-flowfield-totals.good.al | 0 .../use-calcsums-for-flowfield-totals.md | 4 +-- .../use-findset-readonly-by-default.bad.al | 0 .../use-findset-readonly-by-default.good.al | 0 .../use-findset-readonly-by-default.md | 4 +-- .../performance/use-findset-with-next.bad.al | 0 .../performance/use-findset-with-next.good.al | 0 .../performance/use-findset-with-next.md | 4 +-- ...nsert-false-when-skipping-triggers.good.al | 0 ...use-insert-false-when-skipping-triggers.md | 2 +- .../use-isempty-for-existence-checks.bad.al | 0 .../use-isempty-for-existence-checks.good.al | 0 .../use-isempty-for-existence-checks.md | 4 +-- ...e-setloadfields-for-partial-records.bad.al | 0 ...-setloadfields-for-partial-records.good.al | 0 .../use-setloadfields-for-partial-records.md | 4 +-- ...gle-instance-codeunits-for-caching.good.al | 0 ...e-single-instance-codeunits-for-caching.md | 2 +- ...e-strsubstno-for-message-formatting.bad.al | 0 ...-strsubstno-for-message-formatting.good.al | 0 .../use-strsubstno-for-message-formatting.md | 4 +-- ...orary-tables-for-intermediate-data.good.al | 0 ...-temporary-tables-for-intermediate-data.md | 2 +- ...id-sensitive-data-in-error-messages.bad.al | 0 ...d-sensitive-data-in-error-messages.good.al | 0 .../avoid-sensitive-data-in-error-messages.md | 4 +-- ...mpose-secrets-with-secretstrsubstno.bad.al | 0 ...pose-secrets-with-secretstrsubstno.good.al | 0 .../compose-secrets-with-secretstrsubstno.md | 4 +-- ...-sensitive-data-in-event-publishers.bad.al | 0 ...sensitive-data-in-event-publishers.good.al | 0 ...pose-sensitive-data-in-event-publishers.md | 4 +-- .../do-not-put-credentials-in-urls.bad.al | 0 .../do-not-put-credentials-in-urls.good.al | 0 .../do-not-put-credentials-in-urls.md | 4 +-- ...ot-swallow-security-errors-silently.bad.al | 0 ...t-swallow-security-errors-silently.good.al | 0 ...do-not-swallow-security-errors-silently.md | 4 +-- ...-least-privilege-in-permission-sets.bad.al | 0 ...least-privilege-in-permission-sets.good.al | 0 ...llow-least-privilege-in-permission-sets.md | 4 +-- .../never-hardcode-secrets-in-al.bad.al | 0 .../never-hardcode-secrets-in-al.good.al | 0 .../security/never-hardcode-secrets-in-al.md | 4 +-- .../require-https-for-external-calls.bad.al | 0 .../require-https-for-external-calls.good.al | 0 .../require-https-for-external-calls.md | 4 +-- .../set-timeouts-for-external-calls.bad.al | 0 .../set-timeouts-for-external-calls.good.al | 0 .../set-timeouts-for-external-calls.md | 4 +-- ...ect-permissions-for-elevated-access.bad.al | 0 ...ct-permissions-for-elevated-access.good.al | 0 ...ndirect-permissions-for-elevated-access.md | 4 +-- ...permissions-to-grant-minimal-access.bad.al | 0 ...ermissions-to-grant-minimal-access.good.al | 0 ...ent-permissions-to-grant-minimal-access.md | 4 +-- ...rage-for-module-and-company-secrets.bad.al | 0 ...age-for-module-and-company-secrets.good.al | 0 ...-storage-for-module-and-company-secrets.md | 4 +-- ...-nondebuggable-when-parsing-secrets.bad.al | 0 ...nondebuggable-when-parsing-secrets.good.al | 0 .../use-nondebuggable-when-parsing-secrets.md | 4 +-- .../use-secrettext-for-credentials.bad.al | 0 .../use-secrettext-for-credentials.good.al | 0 .../use-secrettext-for-credentials.md | 4 +-- .../use-secrettext-with-httpclient.bad.al | 0 .../use-secrettext-with-httpclient.good.al | 0 .../use-secrettext-with-httpclient.md | 4 +-- samples/README.md | 29 ------------------- skills/read.md | 22 +++++++++++++- skills/write.md | 2 +- 106 files changed, 89 insertions(+), 98 deletions(-) rename samples/performance/add-sift-keys-for-flowfields/good.al => microsoft/knowledge/performance/add-sift-keys-for-flowfields.good.al (100%) rename samples/performance/avoid-calcfields-in-loops/bad.al => microsoft/knowledge/performance/avoid-calcfields-in-loops.bad.al (100%) rename samples/performance/avoid-calcfields-in-loops/good.al => microsoft/knowledge/performance/avoid-calcfields-in-loops.good.al (100%) rename samples/performance/avoid-commit-inside-loops/bad.al => microsoft/knowledge/performance/avoid-commit-inside-loops.bad.al (100%) rename samples/performance/avoid-findfirst-with-next/bad.al => microsoft/knowledge/performance/avoid-findfirst-with-next.bad.al (100%) rename samples/performance/avoid-user-interaction-in-transactions/bad.al => microsoft/knowledge/performance/avoid-user-interaction-in-transactions.bad.al (100%) rename samples/performance/avoid-user-interaction-in-transactions/good.al => microsoft/knowledge/performance/avoid-user-interaction-in-transactions.good.al (100%) rename samples/performance/filter-before-find/bad.al => microsoft/knowledge/performance/filter-before-find.bad.al (100%) rename samples/performance/filter-before-find/good.al => microsoft/knowledge/performance/filter-before-find.good.al (100%) rename samples/performance/keep-event-subscribers-lightweight/bad.al => microsoft/knowledge/performance/keep-event-subscribers-lightweight.bad.al (100%) rename samples/performance/keep-transaction-scope-short/bad.al => microsoft/knowledge/performance/keep-transaction-scope-short.bad.al (100%) rename samples/performance/keep-transaction-scope-short/good.al => microsoft/knowledge/performance/keep-transaction-scope-short.good.al (100%) rename samples/performance/only-fetch-records-you-use/bad.al => microsoft/knowledge/performance/only-fetch-records-you-use.bad.al (100%) rename samples/performance/only-fetch-records-you-use/good.al => microsoft/knowledge/performance/only-fetch-records-you-use.good.al (100%) rename samples/performance/prefer-direct-record-over-recordref/bad.al => microsoft/knowledge/performance/prefer-direct-record-over-recordref.bad.al (100%) rename samples/performance/prefer-direct-record-over-recordref/good.al => microsoft/knowledge/performance/prefer-direct-record-over-recordref.good.al (100%) rename samples/performance/prefer-get-for-primary-key-lookups/bad.al => microsoft/knowledge/performance/prefer-get-for-primary-key-lookups.bad.al (100%) rename samples/performance/prefer-get-for-primary-key-lookups/good.al => microsoft/knowledge/performance/prefer-get-for-primary-key-lookups.good.al (100%) rename samples/performance/set-current-key-to-match-filters/good.al => microsoft/knowledge/performance/set-current-key-to-match-filters.good.al (100%) rename samples/performance/use-addloadfields-in-report-layouts/good.al => microsoft/knowledge/performance/use-addloadfields-in-report-layouts.good.al (100%) rename samples/performance/use-calcsums-for-flowfield-totals/bad.al => microsoft/knowledge/performance/use-calcsums-for-flowfield-totals.bad.al (100%) rename samples/performance/use-calcsums-for-flowfield-totals/good.al => microsoft/knowledge/performance/use-calcsums-for-flowfield-totals.good.al (100%) rename samples/performance/use-findset-readonly-by-default/bad.al => microsoft/knowledge/performance/use-findset-readonly-by-default.bad.al (100%) rename samples/performance/use-findset-readonly-by-default/good.al => microsoft/knowledge/performance/use-findset-readonly-by-default.good.al (100%) rename samples/performance/use-findset-with-next/bad.al => microsoft/knowledge/performance/use-findset-with-next.bad.al (100%) rename samples/performance/use-findset-with-next/good.al => microsoft/knowledge/performance/use-findset-with-next.good.al (100%) rename samples/performance/use-insert-false-when-skipping-triggers/good.al => microsoft/knowledge/performance/use-insert-false-when-skipping-triggers.good.al (100%) rename samples/performance/use-isempty-for-existence-checks/bad.al => microsoft/knowledge/performance/use-isempty-for-existence-checks.bad.al (100%) rename samples/performance/use-isempty-for-existence-checks/good.al => microsoft/knowledge/performance/use-isempty-for-existence-checks.good.al (100%) rename samples/performance/use-setloadfields-for-partial-records/bad.al => microsoft/knowledge/performance/use-setloadfields-for-partial-records.bad.al (100%) rename samples/performance/use-setloadfields-for-partial-records/good.al => microsoft/knowledge/performance/use-setloadfields-for-partial-records.good.al (100%) rename samples/performance/use-single-instance-codeunits-for-caching/good.al => microsoft/knowledge/performance/use-single-instance-codeunits-for-caching.good.al (100%) rename samples/performance/use-strsubstno-for-message-formatting/bad.al => microsoft/knowledge/performance/use-strsubstno-for-message-formatting.bad.al (100%) rename samples/performance/use-strsubstno-for-message-formatting/good.al => microsoft/knowledge/performance/use-strsubstno-for-message-formatting.good.al (100%) rename samples/performance/use-temporary-tables-for-intermediate-data/good.al => microsoft/knowledge/performance/use-temporary-tables-for-intermediate-data.good.al (100%) rename samples/security/avoid-sensitive-data-in-error-messages/bad.al => microsoft/knowledge/security/avoid-sensitive-data-in-error-messages.bad.al (100%) rename samples/security/avoid-sensitive-data-in-error-messages/good.al => microsoft/knowledge/security/avoid-sensitive-data-in-error-messages.good.al (100%) rename samples/security/compose-secrets-with-secretstrsubstno/bad.al => microsoft/knowledge/security/compose-secrets-with-secretstrsubstno.bad.al (100%) rename samples/security/compose-secrets-with-secretstrsubstno/good.al => microsoft/knowledge/security/compose-secrets-with-secretstrsubstno.good.al (100%) rename samples/security/do-not-expose-sensitive-data-in-event-publishers/bad.al => microsoft/knowledge/security/do-not-expose-sensitive-data-in-event-publishers.bad.al (100%) rename samples/security/do-not-expose-sensitive-data-in-event-publishers/good.al => microsoft/knowledge/security/do-not-expose-sensitive-data-in-event-publishers.good.al (100%) rename samples/security/do-not-put-credentials-in-urls/bad.al => microsoft/knowledge/security/do-not-put-credentials-in-urls.bad.al (100%) rename samples/security/do-not-put-credentials-in-urls/good.al => microsoft/knowledge/security/do-not-put-credentials-in-urls.good.al (100%) rename samples/security/do-not-swallow-security-errors-silently/bad.al => microsoft/knowledge/security/do-not-swallow-security-errors-silently.bad.al (100%) rename samples/security/do-not-swallow-security-errors-silently/good.al => microsoft/knowledge/security/do-not-swallow-security-errors-silently.good.al (100%) rename samples/security/follow-least-privilege-in-permission-sets/bad.al => microsoft/knowledge/security/follow-least-privilege-in-permission-sets.bad.al (100%) rename samples/security/follow-least-privilege-in-permission-sets/good.al => microsoft/knowledge/security/follow-least-privilege-in-permission-sets.good.al (100%) rename samples/security/never-hardcode-secrets-in-al/bad.al => microsoft/knowledge/security/never-hardcode-secrets-in-al.bad.al (100%) rename samples/security/never-hardcode-secrets-in-al/good.al => microsoft/knowledge/security/never-hardcode-secrets-in-al.good.al (100%) rename samples/security/require-https-for-external-calls/bad.al => microsoft/knowledge/security/require-https-for-external-calls.bad.al (100%) rename samples/security/require-https-for-external-calls/good.al => microsoft/knowledge/security/require-https-for-external-calls.good.al (100%) rename samples/security/set-timeouts-for-external-calls/bad.al => microsoft/knowledge/security/set-timeouts-for-external-calls.bad.al (100%) rename samples/security/set-timeouts-for-external-calls/good.al => microsoft/knowledge/security/set-timeouts-for-external-calls.good.al (100%) rename samples/security/use-indirect-permissions-for-elevated-access/bad.al => microsoft/knowledge/security/use-indirect-permissions-for-elevated-access.bad.al (100%) rename samples/security/use-indirect-permissions-for-elevated-access/good.al => microsoft/knowledge/security/use-indirect-permissions-for-elevated-access.good.al (100%) rename samples/security/use-inherent-permissions-to-grant-minimal-access/bad.al => microsoft/knowledge/security/use-inherent-permissions-to-grant-minimal-access.bad.al (100%) rename samples/security/use-inherent-permissions-to-grant-minimal-access/good.al => microsoft/knowledge/security/use-inherent-permissions-to-grant-minimal-access.good.al (100%) rename samples/security/use-isolated-storage-for-module-and-company-secrets/bad.al => microsoft/knowledge/security/use-isolated-storage-for-module-and-company-secrets.bad.al (100%) rename samples/security/use-isolated-storage-for-module-and-company-secrets/good.al => microsoft/knowledge/security/use-isolated-storage-for-module-and-company-secrets.good.al (100%) rename samples/security/use-nondebuggable-when-parsing-secrets/bad.al => microsoft/knowledge/security/use-nondebuggable-when-parsing-secrets.bad.al (100%) rename samples/security/use-nondebuggable-when-parsing-secrets/good.al => microsoft/knowledge/security/use-nondebuggable-when-parsing-secrets.good.al (100%) rename samples/security/use-secrettext-for-credentials/bad.al => microsoft/knowledge/security/use-secrettext-for-credentials.bad.al (100%) rename samples/security/use-secrettext-for-credentials/good.al => microsoft/knowledge/security/use-secrettext-for-credentials.good.al (100%) rename samples/security/use-secrettext-with-httpclient/bad.al => microsoft/knowledge/security/use-secrettext-with-httpclient.bad.al (100%) rename samples/security/use-secrettext-with-httpclient/good.al => microsoft/knowledge/security/use-secrettext-with-httpclient.good.al (100%) delete mode 100644 samples/README.md diff --git a/README.md b/README.md index f48e262..68dd7d3 100644 --- a/README.md +++ b/README.md @@ -107,11 +107,11 @@ For the end-to-end flow — from orchestrator trigger through to how output reac ├── /.github/ # Actions and workflows ├── /microsoft/ # Microsoft-endorsed layer │ ├── /knowledge/ # Knowledge files by domain -│ │ └── // +│ │ └── // # Each article: .md + optional .good.al / .bad.al │ └── /skills/ # Microsoft-endorsed action skills ├── /community/ # BC community layer │ ├── /knowledge/ # Knowledge files by domain -│ │ └── // +│ │ └── // # Article + sibling samples, same convention │ └── /skills/ # Community action skills ├── /custom/ # Partner/customer-specific overrides (empty; populated in forks) │ ├── /knowledge/ diff --git a/samples/performance/add-sift-keys-for-flowfields/good.al b/microsoft/knowledge/performance/add-sift-keys-for-flowfields.good.al similarity index 100% rename from samples/performance/add-sift-keys-for-flowfields/good.al rename to microsoft/knowledge/performance/add-sift-keys-for-flowfields.good.al diff --git a/microsoft/knowledge/performance/add-sift-keys-for-flowfields.md b/microsoft/knowledge/performance/add-sift-keys-for-flowfields.md index 2cc6570..e72eb2a 100644 --- a/microsoft/knowledge/performance/add-sift-keys-for-flowfields.md +++ b/microsoft/knowledge/performance/add-sift-keys-for-flowfields.md @@ -19,7 +19,7 @@ CodeCop rule AA0232 checks that FlowFields backed by CalcSums or aggregation Cal For each Sum-style FlowField, ensure the source table has a key whose leading fields match the FlowField's CalcFormula WHERE clause and whose SumIndexFields list includes the summed field. Table extensions adding new FlowFields are responsible for adding the supporting key. -See sample: `samples/performance/add-sift-keys-for-flowfields/good.al`. +See sample: `add-sift-keys-for-flowfields.good.al`. ## Anti Pattern diff --git a/samples/performance/avoid-calcfields-in-loops/bad.al b/microsoft/knowledge/performance/avoid-calcfields-in-loops.bad.al similarity index 100% rename from samples/performance/avoid-calcfields-in-loops/bad.al rename to microsoft/knowledge/performance/avoid-calcfields-in-loops.bad.al diff --git a/samples/performance/avoid-calcfields-in-loops/good.al b/microsoft/knowledge/performance/avoid-calcfields-in-loops.good.al similarity index 100% rename from samples/performance/avoid-calcfields-in-loops/good.al rename to microsoft/knowledge/performance/avoid-calcfields-in-loops.good.al diff --git a/microsoft/knowledge/performance/avoid-calcfields-in-loops.md b/microsoft/knowledge/performance/avoid-calcfields-in-loops.md index e5d9b2b..1be8cfa 100644 --- a/microsoft/knowledge/performance/avoid-calcfields-in-loops.md +++ b/microsoft/knowledge/performance/avoid-calcfields-in-loops.md @@ -19,11 +19,11 @@ CalcFields evaluates one or more FlowFields for the current record by issuing a Move CalcFields out of the iteration. If the total is what you need, use CalcSums on the filtered parent set. If row-by-row FlowField values are needed, reshape the computation so the aggregate runs once — for example by joining against a temporary table populated in a single batched query. -See sample: `samples/performance/avoid-calcfields-in-loops/good.al`. +See sample: `avoid-calcfields-in-loops.good.al`. ## Anti Pattern Calling CalcFields inside `repeat ... until Next() = 0` on a hot parent record is the textbook N+1 pattern. Even a modest parent set size (hundreds of rows) turns into thousands of round-trips. -See sample: `samples/performance/avoid-calcfields-in-loops/bad.al`. +See sample: `avoid-calcfields-in-loops.bad.al`. diff --git a/samples/performance/avoid-commit-inside-loops/bad.al b/microsoft/knowledge/performance/avoid-commit-inside-loops.bad.al similarity index 100% rename from samples/performance/avoid-commit-inside-loops/bad.al rename to microsoft/knowledge/performance/avoid-commit-inside-loops.bad.al diff --git a/microsoft/knowledge/performance/avoid-commit-inside-loops.md b/microsoft/knowledge/performance/avoid-commit-inside-loops.md index 6ee8adb..f8e3943 100644 --- a/microsoft/knowledge/performance/avoid-commit-inside-loops.md +++ b/microsoft/knowledge/performance/avoid-commit-inside-loops.md @@ -23,5 +23,5 @@ If the batch is large enough that a single transaction is untenable, process it Placing Commit inside `repeat ... until Next() = 0` is almost always a mistake: it is unusual for the correctness of the operation to depend on per-row commits, and the cost of starting a new transaction on every row dominates the work. -See sample: `samples/performance/avoid-commit-inside-loops/bad.al`. +See sample: `avoid-commit-inside-loops.bad.al`. diff --git a/samples/performance/avoid-findfirst-with-next/bad.al b/microsoft/knowledge/performance/avoid-findfirst-with-next.bad.al similarity index 100% rename from samples/performance/avoid-findfirst-with-next/bad.al rename to microsoft/knowledge/performance/avoid-findfirst-with-next.bad.al diff --git a/microsoft/knowledge/performance/avoid-findfirst-with-next.md b/microsoft/knowledge/performance/avoid-findfirst-with-next.md index 758384d..bdff419 100644 --- a/microsoft/knowledge/performance/avoid-findfirst-with-next.md +++ b/microsoft/knowledge/performance/avoid-findfirst-with-next.md @@ -23,5 +23,5 @@ Choose the Find variant that matches the operation: FindSet for full iteration, Writing `if Rec.FindFirst() then repeat ... until Rec.Next() = 0` is the canonical AA0233 offender. The loop wastes bandwidth and obscures the author's intent. -See sample: `samples/performance/avoid-findfirst-with-next/bad.al`. +See sample: `avoid-findfirst-with-next.bad.al`. diff --git a/samples/performance/avoid-user-interaction-in-transactions/bad.al b/microsoft/knowledge/performance/avoid-user-interaction-in-transactions.bad.al similarity index 100% rename from samples/performance/avoid-user-interaction-in-transactions/bad.al rename to microsoft/knowledge/performance/avoid-user-interaction-in-transactions.bad.al diff --git a/samples/performance/avoid-user-interaction-in-transactions/good.al b/microsoft/knowledge/performance/avoid-user-interaction-in-transactions.good.al similarity index 100% rename from samples/performance/avoid-user-interaction-in-transactions/good.al rename to microsoft/knowledge/performance/avoid-user-interaction-in-transactions.good.al diff --git a/microsoft/knowledge/performance/avoid-user-interaction-in-transactions.md b/microsoft/knowledge/performance/avoid-user-interaction-in-transactions.md index 1e89004..a3a9041 100644 --- a/microsoft/knowledge/performance/avoid-user-interaction-in-transactions.md +++ b/microsoft/knowledge/performance/avoid-user-interaction-in-transactions.md @@ -19,11 +19,11 @@ Confirm, StrMenu, Message, and any other user-facing dialog pauses execution whi Gather every user decision before the writing phase begins. Once the decisions are known, run the transaction end-to-end without prompts. -See sample: `samples/performance/avoid-user-interaction-in-transactions/good.al`. +See sample: `avoid-user-interaction-in-transactions.good.al`. ## Anti Pattern Calling Confirm or StrMenu from inside an OnInsert, OnModify, or OnDelete trigger — or from any code path that has already started modifying records — blocks on user input while holding locks. -See sample: `samples/performance/avoid-user-interaction-in-transactions/bad.al`. +See sample: `avoid-user-interaction-in-transactions.bad.al`. diff --git a/samples/performance/filter-before-find/bad.al b/microsoft/knowledge/performance/filter-before-find.bad.al similarity index 100% rename from samples/performance/filter-before-find/bad.al rename to microsoft/knowledge/performance/filter-before-find.bad.al diff --git a/samples/performance/filter-before-find/good.al b/microsoft/knowledge/performance/filter-before-find.good.al similarity index 100% rename from samples/performance/filter-before-find/good.al rename to microsoft/knowledge/performance/filter-before-find.good.al diff --git a/microsoft/knowledge/performance/filter-before-find.md b/microsoft/knowledge/performance/filter-before-find.md index cbdfb37..94389b4 100644 --- a/microsoft/knowledge/performance/filter-before-find.md +++ b/microsoft/knowledge/performance/filter-before-find.md @@ -19,11 +19,11 @@ Every call to FindSet, Find, or FindFirst on an unfiltered record variable scans Apply SetRange or SetFilter to narrow the record set before calling FindSet or Find. The filters should match a key on the table (see set-current-key-to-match-filters). When iterating rows that belong to a parent record, set all key-field filters before the find call — never inside the repeat loop. -See sample: `samples/performance/filter-before-find/good.al`. +See sample: `filter-before-find.good.al`. ## Anti Pattern Calling FindSet with no filters and then discarding rows inside the loop with an if-statement forces the platform to read every row of the table before your code even runs. -See sample: `samples/performance/filter-before-find/bad.al`. +See sample: `filter-before-find.bad.al`. diff --git a/samples/performance/keep-event-subscribers-lightweight/bad.al b/microsoft/knowledge/performance/keep-event-subscribers-lightweight.bad.al similarity index 100% rename from samples/performance/keep-event-subscribers-lightweight/bad.al rename to microsoft/knowledge/performance/keep-event-subscribers-lightweight.bad.al diff --git a/microsoft/knowledge/performance/keep-event-subscribers-lightweight.md b/microsoft/knowledge/performance/keep-event-subscribers-lightweight.md index 627370e..25e2ceb 100644 --- a/microsoft/knowledge/performance/keep-event-subscribers-lightweight.md +++ b/microsoft/knowledge/performance/keep-event-subscribers-lightweight.md @@ -23,5 +23,5 @@ Keep subscribers small: guard early with inexpensive checks, defer heavy work to Calling an external web service, running a report, or iterating a large table from inside an event subscriber on a hot publisher makes every operation on that publisher as slow as the heaviest subscriber. -See sample: `samples/performance/keep-event-subscribers-lightweight/bad.al`. +See sample: `keep-event-subscribers-lightweight.bad.al`. diff --git a/samples/performance/keep-transaction-scope-short/bad.al b/microsoft/knowledge/performance/keep-transaction-scope-short.bad.al similarity index 100% rename from samples/performance/keep-transaction-scope-short/bad.al rename to microsoft/knowledge/performance/keep-transaction-scope-short.bad.al diff --git a/samples/performance/keep-transaction-scope-short/good.al b/microsoft/knowledge/performance/keep-transaction-scope-short.good.al similarity index 100% rename from samples/performance/keep-transaction-scope-short/good.al rename to microsoft/knowledge/performance/keep-transaction-scope-short.good.al diff --git a/microsoft/knowledge/performance/keep-transaction-scope-short.md b/microsoft/knowledge/performance/keep-transaction-scope-short.md index 30c141a..704cffc 100644 --- a/microsoft/knowledge/performance/keep-transaction-scope-short.md +++ b/microsoft/knowledge/performance/keep-transaction-scope-short.md @@ -19,11 +19,11 @@ Every write operation runs inside a transaction that holds locks until the trans Perform data reads, calculations, and external integrations outside the transaction whenever possible. Enter the writing phase with all inputs computed, execute the minimum set of Insert, Modify, and Delete calls, and exit. If you have a long-running batch, split it into checkpoints at safe boundaries (see avoid-commit-inside-loops). -See sample: `samples/performance/keep-transaction-scope-short/good.al`. +See sample: `keep-transaction-scope-short.good.al`. ## Anti Pattern Opening a transaction, then performing external web-service calls, heavy report runs, or user-facing dialogs while the locks are held, suspends every other session that needs the same rows for as long as the external operation takes. -See sample: `samples/performance/keep-transaction-scope-short/bad.al`. +See sample: `keep-transaction-scope-short.bad.al`. diff --git a/samples/performance/only-fetch-records-you-use/bad.al b/microsoft/knowledge/performance/only-fetch-records-you-use.bad.al similarity index 100% rename from samples/performance/only-fetch-records-you-use/bad.al rename to microsoft/knowledge/performance/only-fetch-records-you-use.bad.al diff --git a/samples/performance/only-fetch-records-you-use/good.al b/microsoft/knowledge/performance/only-fetch-records-you-use.good.al similarity index 100% rename from samples/performance/only-fetch-records-you-use/good.al rename to microsoft/knowledge/performance/only-fetch-records-you-use.good.al diff --git a/microsoft/knowledge/performance/only-fetch-records-you-use.md b/microsoft/knowledge/performance/only-fetch-records-you-use.md index a1db94f..8c45801 100644 --- a/microsoft/knowledge/performance/only-fetch-records-you-use.md +++ b/microsoft/knowledge/performance/only-fetch-records-you-use.md @@ -19,11 +19,11 @@ CodeCop rule AA0175 flags code that retrieves a record and then does not use it. Retrieve a record only when you need one or more of its field values. When you only need to know whether at least one row matches a filter, use IsEmpty (see use-isempty-for-existence-checks). When you only need a subset of fields, use SetLoadFields (see use-setloadfields-for-partial-records). -See sample: `samples/performance/only-fetch-records-you-use/good.al`. +See sample: `only-fetch-records-you-use.good.al`. ## Anti Pattern Calling FindSet or Get and then ignoring the result, or using it only as a boolean existence test, performs the full fetch and throws the data away. -See sample: `samples/performance/only-fetch-records-you-use/bad.al`. +See sample: `only-fetch-records-you-use.bad.al`. diff --git a/samples/performance/prefer-direct-record-over-recordref/bad.al b/microsoft/knowledge/performance/prefer-direct-record-over-recordref.bad.al similarity index 100% rename from samples/performance/prefer-direct-record-over-recordref/bad.al rename to microsoft/knowledge/performance/prefer-direct-record-over-recordref.bad.al diff --git a/samples/performance/prefer-direct-record-over-recordref/good.al b/microsoft/knowledge/performance/prefer-direct-record-over-recordref.good.al similarity index 100% rename from samples/performance/prefer-direct-record-over-recordref/good.al rename to microsoft/knowledge/performance/prefer-direct-record-over-recordref.good.al diff --git a/microsoft/knowledge/performance/prefer-direct-record-over-recordref.md b/microsoft/knowledge/performance/prefer-direct-record-over-recordref.md index 19402aa..9916ef8 100644 --- a/microsoft/knowledge/performance/prefer-direct-record-over-recordref.md +++ b/microsoft/knowledge/performance/prefer-direct-record-over-recordref.md @@ -19,11 +19,11 @@ RecordRef and FieldRef are the platform's reflection API: they work across table Use Record variables for code paths that target a known table. Reach for RecordRef and FieldRef only when the table is genuinely dynamic (generic export/import, field-agnostic utilities, cross-table integrations). -See sample: `samples/performance/prefer-direct-record-over-recordref/good.al`. +See sample: `prefer-direct-record-over-recordref.good.al`. ## Anti Pattern Using RecordRef as a habit, even when the target table is hardcoded two lines earlier, costs performance and hides intent from reviewers. -See sample: `samples/performance/prefer-direct-record-over-recordref/bad.al`. +See sample: `prefer-direct-record-over-recordref.bad.al`. diff --git a/samples/performance/prefer-get-for-primary-key-lookups/bad.al b/microsoft/knowledge/performance/prefer-get-for-primary-key-lookups.bad.al similarity index 100% rename from samples/performance/prefer-get-for-primary-key-lookups/bad.al rename to microsoft/knowledge/performance/prefer-get-for-primary-key-lookups.bad.al diff --git a/samples/performance/prefer-get-for-primary-key-lookups/good.al b/microsoft/knowledge/performance/prefer-get-for-primary-key-lookups.good.al similarity index 100% rename from samples/performance/prefer-get-for-primary-key-lookups/good.al rename to microsoft/knowledge/performance/prefer-get-for-primary-key-lookups.good.al diff --git a/microsoft/knowledge/performance/prefer-get-for-primary-key-lookups.md b/microsoft/knowledge/performance/prefer-get-for-primary-key-lookups.md index 883922e..6102d47 100644 --- a/microsoft/knowledge/performance/prefer-get-for-primary-key-lookups.md +++ b/microsoft/knowledge/performance/prefer-get-for-primary-key-lookups.md @@ -19,11 +19,11 @@ Get is a direct primary-key lookup: one index seek, one row, done. FindFirst wit When the complete primary key is known, call Get. Use FindFirst only for non-primary-key lookups or when the filter is a partial prefix of the key. -See sample: `samples/performance/prefer-get-for-primary-key-lookups/good.al`. +See sample: `prefer-get-for-primary-key-lookups.good.al`. ## Anti Pattern Setting one SetRange per primary-key field and then calling FindFirst reproduces Get with more typing and slightly worse performance. -See sample: `samples/performance/prefer-get-for-primary-key-lookups/bad.al`. +See sample: `prefer-get-for-primary-key-lookups.bad.al`. diff --git a/samples/performance/set-current-key-to-match-filters/good.al b/microsoft/knowledge/performance/set-current-key-to-match-filters.good.al similarity index 100% rename from samples/performance/set-current-key-to-match-filters/good.al rename to microsoft/knowledge/performance/set-current-key-to-match-filters.good.al diff --git a/microsoft/knowledge/performance/set-current-key-to-match-filters.md b/microsoft/knowledge/performance/set-current-key-to-match-filters.md index e9dc2b7..87b4bf9 100644 --- a/microsoft/knowledge/performance/set-current-key-to-match-filters.md +++ b/microsoft/knowledge/performance/set-current-key-to-match-filters.md @@ -19,7 +19,7 @@ AL chooses a key for a Find call based on the current SetCurrentKey selection. W Call SetCurrentKey with the fields you filter and sort on, in the order they appear in a table key. If no suitable key exists, add one via a table extension rather than relying on an unsupported filter pattern. -See sample: `samples/performance/set-current-key-to-match-filters/good.al`. +See sample: `set-current-key-to-match-filters.good.al`. ## Anti Pattern diff --git a/samples/performance/use-addloadfields-in-report-layouts/good.al b/microsoft/knowledge/performance/use-addloadfields-in-report-layouts.good.al similarity index 100% rename from samples/performance/use-addloadfields-in-report-layouts/good.al rename to microsoft/knowledge/performance/use-addloadfields-in-report-layouts.good.al diff --git a/microsoft/knowledge/performance/use-addloadfields-in-report-layouts.md b/microsoft/knowledge/performance/use-addloadfields-in-report-layouts.md index 38e252d..4331947 100644 --- a/microsoft/knowledge/performance/use-addloadfields-in-report-layouts.md +++ b/microsoft/knowledge/performance/use-addloadfields-in-report-layouts.md @@ -19,7 +19,7 @@ Reports iterate a dataitem's record automatically; the developer does not contro In each dataitem's OnPreDataItem trigger, call AddLoadFields for every field used by the layout, by the dataitem's triggers, and by any code that runs in the row-level event hooks. If the layout uses a FlowField, also ensure CalcFields is called and that the underlying key is loaded (see add-sift-keys-for-flowfields). -See sample: `samples/performance/use-addloadfields-in-report-layouts/good.al`. +See sample: `use-addloadfields-in-report-layouts.good.al`. ## Anti Pattern diff --git a/samples/performance/use-calcsums-for-flowfield-totals/bad.al b/microsoft/knowledge/performance/use-calcsums-for-flowfield-totals.bad.al similarity index 100% rename from samples/performance/use-calcsums-for-flowfield-totals/bad.al rename to microsoft/knowledge/performance/use-calcsums-for-flowfield-totals.bad.al diff --git a/samples/performance/use-calcsums-for-flowfield-totals/good.al b/microsoft/knowledge/performance/use-calcsums-for-flowfield-totals.good.al similarity index 100% rename from samples/performance/use-calcsums-for-flowfield-totals/good.al rename to microsoft/knowledge/performance/use-calcsums-for-flowfield-totals.good.al diff --git a/microsoft/knowledge/performance/use-calcsums-for-flowfield-totals.md b/microsoft/knowledge/performance/use-calcsums-for-flowfield-totals.md index 66b1c65..19a2e2c 100644 --- a/microsoft/knowledge/performance/use-calcsums-for-flowfield-totals.md +++ b/microsoft/knowledge/performance/use-calcsums-for-flowfield-totals.md @@ -19,11 +19,11 @@ When the task is to compute a sum over a filtered set, CalcSums lets the platfor Set the required filters on the record, then call CalcSums on the field you want aggregated. Ensure the table has a key whose SumIndexFields includes the summed field and whose key prefix matches the filters (see add-sift-keys-for-flowfields). -See sample: `samples/performance/use-calcsums-for-flowfield-totals/good.al`. +See sample: `use-calcsums-for-flowfield-totals.good.al`. ## Anti Pattern Looping a filtered set with FindSet and adding a field to an accumulator on every iteration performs work in AL that SQL already knows how to do in one aggregate query. -See sample: `samples/performance/use-calcsums-for-flowfield-totals/bad.al`. +See sample: `use-calcsums-for-flowfield-totals.bad.al`. diff --git a/samples/performance/use-findset-readonly-by-default/bad.al b/microsoft/knowledge/performance/use-findset-readonly-by-default.bad.al similarity index 100% rename from samples/performance/use-findset-readonly-by-default/bad.al rename to microsoft/knowledge/performance/use-findset-readonly-by-default.bad.al diff --git a/samples/performance/use-findset-readonly-by-default/good.al b/microsoft/knowledge/performance/use-findset-readonly-by-default.good.al similarity index 100% rename from samples/performance/use-findset-readonly-by-default/good.al rename to microsoft/knowledge/performance/use-findset-readonly-by-default.good.al diff --git a/microsoft/knowledge/performance/use-findset-readonly-by-default.md b/microsoft/knowledge/performance/use-findset-readonly-by-default.md index ebf5b9d..64cbe72 100644 --- a/microsoft/knowledge/performance/use-findset-readonly-by-default.md +++ b/microsoft/knowledge/performance/use-findset-readonly-by-default.md @@ -19,11 +19,11 @@ FindSet has two modes: FindSet() and FindSet(false) are read-only and take no wr Call FindSet with no arguments when the loop only reads field values. Pass `true` only when the same loop is expected to call Modify, Delete, or Rename on the record, and the correctness of the operation depends on the table being locked for the full iteration. -See sample: `samples/performance/use-findset-readonly-by-default/good.al`. +See sample: `use-findset-readonly-by-default.good.al`. ## Anti Pattern Writing FindSet(true) reflexively for every iteration forces the platform to take a LockTable on every call, even when the loop only reads values. The older two-parameter signature `FindSet(ForUpdate, UpdateKey)` is obsolete and must not be used. -See sample: `samples/performance/use-findset-readonly-by-default/bad.al`. +See sample: `use-findset-readonly-by-default.bad.al`. diff --git a/samples/performance/use-findset-with-next/bad.al b/microsoft/knowledge/performance/use-findset-with-next.bad.al similarity index 100% rename from samples/performance/use-findset-with-next/bad.al rename to microsoft/knowledge/performance/use-findset-with-next.bad.al diff --git a/samples/performance/use-findset-with-next/good.al b/microsoft/knowledge/performance/use-findset-with-next.good.al similarity index 100% rename from samples/performance/use-findset-with-next/good.al rename to microsoft/knowledge/performance/use-findset-with-next.good.al diff --git a/microsoft/knowledge/performance/use-findset-with-next.md b/microsoft/knowledge/performance/use-findset-with-next.md index a05900e..f15912f 100644 --- a/microsoft/knowledge/performance/use-findset-with-next.md +++ b/microsoft/knowledge/performance/use-findset-with-next.md @@ -19,11 +19,11 @@ When iterating over a filtered set of records with repeat-until, use FindSet tog Call FindSet to start the iteration and Next to advance. Guard the loop with the standard `if FindSet() then ... until Next() = 0` idiom so callers can still handle the empty-set case. -See sample: `samples/performance/use-findset-with-next/good.al`. +See sample: `use-findset-with-next.good.al`. ## Anti Pattern Starting a repeat-until loop with FindFirst or FindLast reads only one row and then calls Next on an iterator that was not intended for full-set traversal. The platform pays extra work to fetch the single row and the loop silhouette is misleading to reviewers. -See sample: `samples/performance/use-findset-with-next/bad.al`. +See sample: `use-findset-with-next.bad.al`. diff --git a/samples/performance/use-insert-false-when-skipping-triggers/good.al b/microsoft/knowledge/performance/use-insert-false-when-skipping-triggers.good.al similarity index 100% rename from samples/performance/use-insert-false-when-skipping-triggers/good.al rename to microsoft/knowledge/performance/use-insert-false-when-skipping-triggers.good.al diff --git a/microsoft/knowledge/performance/use-insert-false-when-skipping-triggers.md b/microsoft/knowledge/performance/use-insert-false-when-skipping-triggers.md index a3311cf..748c6a2 100644 --- a/microsoft/knowledge/performance/use-insert-false-when-skipping-triggers.md +++ b/microsoft/knowledge/performance/use-insert-false-when-skipping-triggers.md @@ -19,7 +19,7 @@ Insert, Modify, and Delete accept a boolean that controls whether the table's On Call Insert(true), Modify(true), or Delete(true) when the table's trigger logic is part of the operation's semantics. Call Insert(false), Modify(false), or Delete(false) when the operation is bulk data movement or temporary-table manipulation and the trigger would duplicate work or fire invalid side effects. -See sample: `samples/performance/use-insert-false-when-skipping-triggers/good.al`. +See sample: `use-insert-false-when-skipping-triggers.good.al`. ## Anti Pattern diff --git a/samples/performance/use-isempty-for-existence-checks/bad.al b/microsoft/knowledge/performance/use-isempty-for-existence-checks.bad.al similarity index 100% rename from samples/performance/use-isempty-for-existence-checks/bad.al rename to microsoft/knowledge/performance/use-isempty-for-existence-checks.bad.al diff --git a/samples/performance/use-isempty-for-existence-checks/good.al b/microsoft/knowledge/performance/use-isempty-for-existence-checks.good.al similarity index 100% rename from samples/performance/use-isempty-for-existence-checks/good.al rename to microsoft/knowledge/performance/use-isempty-for-existence-checks.good.al diff --git a/microsoft/knowledge/performance/use-isempty-for-existence-checks.md b/microsoft/knowledge/performance/use-isempty-for-existence-checks.md index 268f690..7199a74 100644 --- a/microsoft/knowledge/performance/use-isempty-for-existence-checks.md +++ b/microsoft/knowledge/performance/use-isempty-for-existence-checks.md @@ -19,11 +19,11 @@ IsEmpty is the cheapest way to answer whether at least one row matches the curre Use `if not Rec.IsEmpty() then ...` for existence checks. Reserve Count for cases where the exact number of rows is needed, and FindFirst for cases where you actually want the row's field values. -See sample: `samples/performance/use-isempty-for-existence-checks/good.al`. +See sample: `use-isempty-for-existence-checks.good.al`. ## Anti Pattern `if Rec.Count() > 0` iterates the whole set just to answer a yes/no question. `if Rec.FindFirst() then` loads an entire row of data the caller never reads. -See sample: `samples/performance/use-isempty-for-existence-checks/bad.al`. +See sample: `use-isempty-for-existence-checks.bad.al`. diff --git a/samples/performance/use-setloadfields-for-partial-records/bad.al b/microsoft/knowledge/performance/use-setloadfields-for-partial-records.bad.al similarity index 100% rename from samples/performance/use-setloadfields-for-partial-records/bad.al rename to microsoft/knowledge/performance/use-setloadfields-for-partial-records.bad.al diff --git a/samples/performance/use-setloadfields-for-partial-records/good.al b/microsoft/knowledge/performance/use-setloadfields-for-partial-records.good.al similarity index 100% rename from samples/performance/use-setloadfields-for-partial-records/good.al rename to microsoft/knowledge/performance/use-setloadfields-for-partial-records.good.al diff --git a/microsoft/knowledge/performance/use-setloadfields-for-partial-records.md b/microsoft/knowledge/performance/use-setloadfields-for-partial-records.md index a11499a..e112fc7 100644 --- a/microsoft/knowledge/performance/use-setloadfields-for-partial-records.md +++ b/microsoft/knowledge/performance/use-setloadfields-for-partial-records.md @@ -19,11 +19,11 @@ SetLoadFields instructs the platform to hydrate only the listed fields on a reco Call SetLoadFields before FindSet, FindFirst, or Get whenever the code path only reads a subset of fields. List every field that is read during the operation, including fields used in filters, calculations, and downstream function calls. Omitting a field that is later accessed triggers a second round-trip. -See sample: `samples/performance/use-setloadfields-for-partial-records/good.al`. +See sample: `use-setloadfields-for-partial-records.good.al`. ## Anti Pattern Iterating a large set and reading only two or three fields without SetLoadFields forces the platform to transport every column for every row, including BLOBs and unused text fields. -See sample: `samples/performance/use-setloadfields-for-partial-records/bad.al`. +See sample: `use-setloadfields-for-partial-records.bad.al`. diff --git a/samples/performance/use-single-instance-codeunits-for-caching/good.al b/microsoft/knowledge/performance/use-single-instance-codeunits-for-caching.good.al similarity index 100% rename from samples/performance/use-single-instance-codeunits-for-caching/good.al rename to microsoft/knowledge/performance/use-single-instance-codeunits-for-caching.good.al diff --git a/microsoft/knowledge/performance/use-single-instance-codeunits-for-caching.md b/microsoft/knowledge/performance/use-single-instance-codeunits-for-caching.md index 3f658d0..ca98596 100644 --- a/microsoft/knowledge/performance/use-single-instance-codeunits-for-caching.md +++ b/microsoft/knowledge/performance/use-single-instance-codeunits-for-caching.md @@ -19,7 +19,7 @@ A SingleInstance codeunit lives once per session. Variables on it survive across Store long-lived, read-often, rarely-changing data on a SingleInstance codeunit, populated lazily on first access. Keep the cached footprint small: a handful of booleans, a setup record, a few derived values. Be explicit about invalidation if the source can change during the session. -See sample: `samples/performance/use-single-instance-codeunits-for-caching/good.al`. +See sample: `use-single-instance-codeunits-for-caching.good.al`. ## Anti Pattern diff --git a/samples/performance/use-strsubstno-for-message-formatting/bad.al b/microsoft/knowledge/performance/use-strsubstno-for-message-formatting.bad.al similarity index 100% rename from samples/performance/use-strsubstno-for-message-formatting/bad.al rename to microsoft/knowledge/performance/use-strsubstno-for-message-formatting.bad.al diff --git a/samples/performance/use-strsubstno-for-message-formatting/good.al b/microsoft/knowledge/performance/use-strsubstno-for-message-formatting.good.al similarity index 100% rename from samples/performance/use-strsubstno-for-message-formatting/good.al rename to microsoft/knowledge/performance/use-strsubstno-for-message-formatting.good.al diff --git a/microsoft/knowledge/performance/use-strsubstno-for-message-formatting.md b/microsoft/knowledge/performance/use-strsubstno-for-message-formatting.md index a5fed55..6e2b3f6 100644 --- a/microsoft/knowledge/performance/use-strsubstno-for-message-formatting.md +++ b/microsoft/knowledge/performance/use-strsubstno-for-message-formatting.md @@ -19,11 +19,11 @@ StrSubstNo formats values into a placeholder template in a single call. Manual c Declare the template as a Label (so it can be localized) and format with StrSubstNo. Pass values in the order the placeholders expect; StrSubstNo handles locale-sensitive conversions consistently. -See sample: `samples/performance/use-strsubstno-for-message-formatting/good.al`. +See sample: `use-strsubstno-for-message-formatting.good.al`. ## Anti Pattern Building a user-facing string by concatenating record field values with string literals ignores locale rules and allocates more than necessary. -See sample: `samples/performance/use-strsubstno-for-message-formatting/bad.al`. +See sample: `use-strsubstno-for-message-formatting.bad.al`. diff --git a/samples/performance/use-temporary-tables-for-intermediate-data/good.al b/microsoft/knowledge/performance/use-temporary-tables-for-intermediate-data.good.al similarity index 100% rename from samples/performance/use-temporary-tables-for-intermediate-data/good.al rename to microsoft/knowledge/performance/use-temporary-tables-for-intermediate-data.good.al diff --git a/microsoft/knowledge/performance/use-temporary-tables-for-intermediate-data.md b/microsoft/knowledge/performance/use-temporary-tables-for-intermediate-data.md index d87a054..3c12938 100644 --- a/microsoft/knowledge/performance/use-temporary-tables-for-intermediate-data.md +++ b/microsoft/knowledge/performance/use-temporary-tables-for-intermediate-data.md @@ -19,7 +19,7 @@ Temporary tables live in memory, not in SQL. They are the correct primary data s Declare the record variable with `temporary` when the data is scratch. Populate it with Insert(false) to avoid firing triggers. Clear the table explicitly with DeleteAll when the variable's scope is long-lived (a SingleInstance codeunit or a reused session variable) and needs to be reset between uses. -See sample: `samples/performance/use-temporary-tables-for-intermediate-data/good.al`. +See sample: `use-temporary-tables-for-intermediate-data.good.al`. ## Anti Pattern diff --git a/samples/security/avoid-sensitive-data-in-error-messages/bad.al b/microsoft/knowledge/security/avoid-sensitive-data-in-error-messages.bad.al similarity index 100% rename from samples/security/avoid-sensitive-data-in-error-messages/bad.al rename to microsoft/knowledge/security/avoid-sensitive-data-in-error-messages.bad.al diff --git a/samples/security/avoid-sensitive-data-in-error-messages/good.al b/microsoft/knowledge/security/avoid-sensitive-data-in-error-messages.good.al similarity index 100% rename from samples/security/avoid-sensitive-data-in-error-messages/good.al rename to microsoft/knowledge/security/avoid-sensitive-data-in-error-messages.good.al diff --git a/microsoft/knowledge/security/avoid-sensitive-data-in-error-messages.md b/microsoft/knowledge/security/avoid-sensitive-data-in-error-messages.md index e8ce0c1..75d78cb 100644 --- a/microsoft/knowledge/security/avoid-sensitive-data-in-error-messages.md +++ b/microsoft/knowledge/security/avoid-sensitive-data-in-error-messages.md @@ -19,11 +19,11 @@ Errors surfaced to end users are routinely forwarded to support systems, capture Raise end-user errors using localized Labels that describe the condition without naming infrastructure. Emit the actual detail (exception text, endpoint, correlation id) through the application's internal logging channel, where audience and retention are controlled. -See sample: `samples/security/avoid-sensitive-data-in-error-messages/good.al`. +See sample: `avoid-sensitive-data-in-error-messages.good.al`. ## Anti Pattern Error('Failed to connect to Server=PROD-SQL01;Database=NAV;User=admin: %1', Ex.Message); — every support ticket now carries the server name, database name, and service account. -See sample: `samples/security/avoid-sensitive-data-in-error-messages/bad.al`. +See sample: `avoid-sensitive-data-in-error-messages.bad.al`. diff --git a/samples/security/compose-secrets-with-secretstrsubstno/bad.al b/microsoft/knowledge/security/compose-secrets-with-secretstrsubstno.bad.al similarity index 100% rename from samples/security/compose-secrets-with-secretstrsubstno/bad.al rename to microsoft/knowledge/security/compose-secrets-with-secretstrsubstno.bad.al diff --git a/samples/security/compose-secrets-with-secretstrsubstno/good.al b/microsoft/knowledge/security/compose-secrets-with-secretstrsubstno.good.al similarity index 100% rename from samples/security/compose-secrets-with-secretstrsubstno/good.al rename to microsoft/knowledge/security/compose-secrets-with-secretstrsubstno.good.al diff --git a/microsoft/knowledge/security/compose-secrets-with-secretstrsubstno.md b/microsoft/knowledge/security/compose-secrets-with-secretstrsubstno.md index bc02ed9..0d88c52 100644 --- a/microsoft/knowledge/security/compose-secrets-with-secretstrsubstno.md +++ b/microsoft/knowledge/security/compose-secrets-with-secretstrsubstno.md @@ -19,11 +19,11 @@ SecretStrSubstNo is the SecretText analogue of StrSubstNo. The template is a reg Format SecretText templates with SecretStrSubstNo. This is the correct primitive for building authorization headers, secret URIs, and any other formatted string that embeds a SecretText. Provide the static parts of the template as a regular string literal; only the substitutions carry the secret value. -See sample: `samples/security/compose-secrets-with-secretstrsubstno/good.al`. +See sample: `compose-secrets-with-secretstrsubstno.good.al`. ## Anti Pattern Using StrSubstNo (or plain string concatenation) on a plain-Text token to build an authorization header. The result is a Text containing the secret in plaintext, visible in the debugger, inspectable in snapshot debug sessions, and captured by any logging the caller does not control. SecretText should have been used end-to-end. -See sample: `samples/security/compose-secrets-with-secretstrsubstno/bad.al`. +See sample: `compose-secrets-with-secretstrsubstno.bad.al`. diff --git a/samples/security/do-not-expose-sensitive-data-in-event-publishers/bad.al b/microsoft/knowledge/security/do-not-expose-sensitive-data-in-event-publishers.bad.al similarity index 100% rename from samples/security/do-not-expose-sensitive-data-in-event-publishers/bad.al rename to microsoft/knowledge/security/do-not-expose-sensitive-data-in-event-publishers.bad.al diff --git a/samples/security/do-not-expose-sensitive-data-in-event-publishers/good.al b/microsoft/knowledge/security/do-not-expose-sensitive-data-in-event-publishers.good.al similarity index 100% rename from samples/security/do-not-expose-sensitive-data-in-event-publishers/good.al rename to microsoft/knowledge/security/do-not-expose-sensitive-data-in-event-publishers.good.al diff --git a/microsoft/knowledge/security/do-not-expose-sensitive-data-in-event-publishers.md b/microsoft/knowledge/security/do-not-expose-sensitive-data-in-event-publishers.md index f7c039a..b8907a8 100644 --- a/microsoft/knowledge/security/do-not-expose-sensitive-data-in-event-publishers.md +++ b/microsoft/knowledge/security/do-not-expose-sensitive-data-in-event-publishers.md @@ -19,11 +19,11 @@ Events in AL are extensibility contracts. Every subscriber — third-party, inte Design event signatures to carry only the data a subscriber legitimately needs. Do not pass SecretText, credential material, or flags the publisher depends on for access control. If a subscriber needs to veto an action, model it as a separate OnBefore event whose Handled pattern is documented — not as a general-purpose var Boolean callers can flip. -See sample: `samples/security/do-not-expose-sensitive-data-in-event-publishers/good.al`. +See sample: `do-not-expose-sensitive-data-in-event-publishers.good.al`. ## Anti Pattern An OnBeforeElevateAccess publisher that exposes `var CanAccess: Boolean` — any subscriber installed on the tenant can flip it to true and escalate. Or a publisher that passes a SecretText parameter it obtained internally, handing it to every subscriber. -See sample: `samples/security/do-not-expose-sensitive-data-in-event-publishers/bad.al`. +See sample: `do-not-expose-sensitive-data-in-event-publishers.bad.al`. diff --git a/samples/security/do-not-put-credentials-in-urls/bad.al b/microsoft/knowledge/security/do-not-put-credentials-in-urls.bad.al similarity index 100% rename from samples/security/do-not-put-credentials-in-urls/bad.al rename to microsoft/knowledge/security/do-not-put-credentials-in-urls.bad.al diff --git a/samples/security/do-not-put-credentials-in-urls/good.al b/microsoft/knowledge/security/do-not-put-credentials-in-urls.good.al similarity index 100% rename from samples/security/do-not-put-credentials-in-urls/good.al rename to microsoft/knowledge/security/do-not-put-credentials-in-urls.good.al diff --git a/microsoft/knowledge/security/do-not-put-credentials-in-urls.md b/microsoft/knowledge/security/do-not-put-credentials-in-urls.md index 2d1bc2e..dc3843e 100644 --- a/microsoft/knowledge/security/do-not-put-credentials-in-urls.md +++ b/microsoft/knowledge/security/do-not-put-credentials-in-urls.md @@ -19,11 +19,11 @@ URL query strings and path segments are routinely captured in web-server access Transport credentials in Authorization headers, carried as SecretText end-to-end (see use-secrettext-with-httpclient). Where the URI itself must carry a secret (for example, a pre-signed URL), build it with SecretStrSubstNo and pass it via SetSecretRequestUri so it is never materialized as Text. -See sample: `samples/security/do-not-put-credentials-in-urls/good.al`. +See sample: `do-not-put-credentials-in-urls.good.al`. ## Anti Pattern Appending '?api_key=' + Key to a request URL, or embedding a token in a path segment, then calling HttpClient.Get with the resulting Text URL. -See sample: `samples/security/do-not-put-credentials-in-urls/bad.al`. +See sample: `do-not-put-credentials-in-urls.bad.al`. diff --git a/samples/security/do-not-swallow-security-errors-silently/bad.al b/microsoft/knowledge/security/do-not-swallow-security-errors-silently.bad.al similarity index 100% rename from samples/security/do-not-swallow-security-errors-silently/bad.al rename to microsoft/knowledge/security/do-not-swallow-security-errors-silently.bad.al diff --git a/samples/security/do-not-swallow-security-errors-silently/good.al b/microsoft/knowledge/security/do-not-swallow-security-errors-silently.good.al similarity index 100% rename from samples/security/do-not-swallow-security-errors-silently/good.al rename to microsoft/knowledge/security/do-not-swallow-security-errors-silently.good.al diff --git a/microsoft/knowledge/security/do-not-swallow-security-errors-silently.md b/microsoft/knowledge/security/do-not-swallow-security-errors-silently.md index e513063..fa4af42 100644 --- a/microsoft/knowledge/security/do-not-swallow-security-errors-silently.md +++ b/microsoft/knowledge/security/do-not-swallow-security-errors-silently.md @@ -19,11 +19,11 @@ Authentication failures, permission denials, and unexpected error paths in secur Use TryFunctions to contain errors around security-relevant work, but always log the failure (category, GetLastErrorText, and enough context to identify the operation) before deciding whether to surface a user-facing error. Never discard a caught security error without a trace. -See sample: `samples/security/do-not-swallow-security-errors-silently/good.al`. +See sample: `do-not-swallow-security-errors-silently.good.al`. ## Anti Pattern `if not TryAuthenticate() then exit;` with no logging and no user-facing error. An authentication-bypass attempt, a revoked credential, and a transient network glitch are now indistinguishable. -See sample: `samples/security/do-not-swallow-security-errors-silently/bad.al`. +See sample: `do-not-swallow-security-errors-silently.bad.al`. diff --git a/samples/security/follow-least-privilege-in-permission-sets/bad.al b/microsoft/knowledge/security/follow-least-privilege-in-permission-sets.bad.al similarity index 100% rename from samples/security/follow-least-privilege-in-permission-sets/bad.al rename to microsoft/knowledge/security/follow-least-privilege-in-permission-sets.bad.al diff --git a/samples/security/follow-least-privilege-in-permission-sets/good.al b/microsoft/knowledge/security/follow-least-privilege-in-permission-sets.good.al similarity index 100% rename from samples/security/follow-least-privilege-in-permission-sets/good.al rename to microsoft/knowledge/security/follow-least-privilege-in-permission-sets.good.al diff --git a/microsoft/knowledge/security/follow-least-privilege-in-permission-sets.md b/microsoft/knowledge/security/follow-least-privilege-in-permission-sets.md index 47d70ad..d33c7be 100644 --- a/microsoft/knowledge/security/follow-least-privilege-in-permission-sets.md +++ b/microsoft/knowledge/security/follow-least-privilege-in-permission-sets.md @@ -19,11 +19,11 @@ Permission sets define the tabledata and object rights granted to every user or Enumerate the specific tabledata objects a role needs and grant only the letters (R, I, M, D) that role genuinely uses. A sales order-entry role typically needs RIM on Sales Header, RIMD on Sales Line, and R on Customer — not blanket RIMD. Permission sets SHOULD be granular and role-shaped; a single permission set that covers every role in an extension is a design smell. -See sample: `samples/security/follow-least-privilege-in-permission-sets/good.al`. +See sample: `follow-least-privilege-in-permission-sets.good.al`. ## Anti Pattern Granting `tabledata * = RIMD` (or any wildcard with I, M, or D) in a permission set. This bypasses any meaningful separation of duties the extension could enforce and gives unreviewed code paths the ability to insert, modify, and delete on any table. -See sample: `samples/security/follow-least-privilege-in-permission-sets/bad.al`. +See sample: `follow-least-privilege-in-permission-sets.bad.al`. diff --git a/samples/security/never-hardcode-secrets-in-al/bad.al b/microsoft/knowledge/security/never-hardcode-secrets-in-al.bad.al similarity index 100% rename from samples/security/never-hardcode-secrets-in-al/bad.al rename to microsoft/knowledge/security/never-hardcode-secrets-in-al.bad.al diff --git a/samples/security/never-hardcode-secrets-in-al/good.al b/microsoft/knowledge/security/never-hardcode-secrets-in-al.good.al similarity index 100% rename from samples/security/never-hardcode-secrets-in-al/good.al rename to microsoft/knowledge/security/never-hardcode-secrets-in-al.good.al diff --git a/microsoft/knowledge/security/never-hardcode-secrets-in-al.md b/microsoft/knowledge/security/never-hardcode-secrets-in-al.md index a3157bc..4be50aa 100644 --- a/microsoft/knowledge/security/never-hardcode-secrets-in-al.md +++ b/microsoft/knowledge/security/never-hardcode-secrets-in-al.md @@ -19,11 +19,11 @@ A secret embedded in AL source — API key, password, connection string, token Retrieve secrets at runtime from a protected store: Azure Key Vault for production workloads (see prefer-azure-key-vault-for-production-secrets) or IsolatedStorage for tenant-local encrypted values (see use-isolated-storage-for-module-and-company-secrets). Carry the retrieved value in a SecretText variable end-to-end (see use-secrettext-for-credentials). -See sample: `samples/security/never-hardcode-secrets-in-al/good.al`. +See sample: `never-hardcode-secrets-in-al.good.al`. ## Anti Pattern Assigning a secret literal to a Text, Code, or Label variable (including labels marked as constants). The secret is now part of the compiled app and indistinguishable from non-sensitive content to callers and tools. -See sample: `samples/security/never-hardcode-secrets-in-al/bad.al`. +See sample: `never-hardcode-secrets-in-al.bad.al`. diff --git a/samples/security/require-https-for-external-calls/bad.al b/microsoft/knowledge/security/require-https-for-external-calls.bad.al similarity index 100% rename from samples/security/require-https-for-external-calls/bad.al rename to microsoft/knowledge/security/require-https-for-external-calls.bad.al diff --git a/samples/security/require-https-for-external-calls/good.al b/microsoft/knowledge/security/require-https-for-external-calls.good.al similarity index 100% rename from samples/security/require-https-for-external-calls/good.al rename to microsoft/knowledge/security/require-https-for-external-calls.good.al diff --git a/microsoft/knowledge/security/require-https-for-external-calls.md b/microsoft/knowledge/security/require-https-for-external-calls.md index b1be633..a026015 100644 --- a/microsoft/knowledge/security/require-https-for-external-calls.md +++ b/microsoft/knowledge/security/require-https-for-external-calls.md @@ -19,11 +19,11 @@ HttpClient can issue requests over plaintext HTTP as easily as over HTTPS. A req Call external services exclusively over https://. When the destination is configurable, validate at runtime that the scheme is https before issuing the request, and fail closed with a clear (non-disclosing) error otherwise. -See sample: `samples/security/require-https-for-external-calls/good.al`. +See sample: `require-https-for-external-calls.good.al`. ## Anti Pattern Issuing HttpClient.Get('http://...'), or accepting an arbitrary user-supplied URL and passing it straight to HttpClient without scheme validation. -See sample: `samples/security/require-https-for-external-calls/bad.al`. +See sample: `require-https-for-external-calls.bad.al`. diff --git a/samples/security/set-timeouts-for-external-calls/bad.al b/microsoft/knowledge/security/set-timeouts-for-external-calls.bad.al similarity index 100% rename from samples/security/set-timeouts-for-external-calls/bad.al rename to microsoft/knowledge/security/set-timeouts-for-external-calls.bad.al diff --git a/samples/security/set-timeouts-for-external-calls/good.al b/microsoft/knowledge/security/set-timeouts-for-external-calls.good.al similarity index 100% rename from samples/security/set-timeouts-for-external-calls/good.al rename to microsoft/knowledge/security/set-timeouts-for-external-calls.good.al diff --git a/microsoft/knowledge/security/set-timeouts-for-external-calls.md b/microsoft/knowledge/security/set-timeouts-for-external-calls.md index 581a321..28d83b0 100644 --- a/microsoft/knowledge/security/set-timeouts-for-external-calls.md +++ b/microsoft/knowledge/security/set-timeouts-for-external-calls.md @@ -19,11 +19,11 @@ An HttpClient with no explicit timeout relies on defaults that may be long enoug Set HttpClient.Timeout to a bounded value (seconds, not minutes) that reflects the SLA of the dependency. Handle the timeout error without leaking endpoint details to end users (see avoid-sensitive-data-in-error-messages). -See sample: `samples/security/set-timeouts-for-external-calls/good.al`. +See sample: `set-timeouts-for-external-calls.good.al`. ## Anti Pattern Issuing HttpClient requests without setting Timeout and without a timeout-handling branch. A slow dependency now has an unbounded blast radius inside the extension. -See sample: `samples/security/set-timeouts-for-external-calls/bad.al`. +See sample: `set-timeouts-for-external-calls.bad.al`. diff --git a/samples/security/use-indirect-permissions-for-elevated-access/bad.al b/microsoft/knowledge/security/use-indirect-permissions-for-elevated-access.bad.al similarity index 100% rename from samples/security/use-indirect-permissions-for-elevated-access/bad.al rename to microsoft/knowledge/security/use-indirect-permissions-for-elevated-access.bad.al diff --git a/samples/security/use-indirect-permissions-for-elevated-access/good.al b/microsoft/knowledge/security/use-indirect-permissions-for-elevated-access.good.al similarity index 100% rename from samples/security/use-indirect-permissions-for-elevated-access/good.al rename to microsoft/knowledge/security/use-indirect-permissions-for-elevated-access.good.al diff --git a/microsoft/knowledge/security/use-indirect-permissions-for-elevated-access.md b/microsoft/knowledge/security/use-indirect-permissions-for-elevated-access.md index da8d860..ecf648b 100644 --- a/microsoft/knowledge/security/use-indirect-permissions-for-elevated-access.md +++ b/microsoft/knowledge/security/use-indirect-permissions-for-elevated-access.md @@ -19,11 +19,11 @@ Indirect permissions (ri, ii, mi, di) let a procedure perform an operation again Where a module exposes a controlled write or delete against a sensitive table, grant the codeunit (or the helper permission set it assumes) the indirect permission (mi, di) it requires, keep direct permissions minimal, and document why the elevation is justified. The helper MUST validate its inputs and the caller's identity before performing the elevated work. -See sample: `samples/security/use-indirect-permissions-for-elevated-access/good.al`. +See sample: `use-indirect-permissions-for-elevated-access.good.al`. ## Anti Pattern Granting direct M or D on a sensitive tabledata to every role that might invoke a helper, because authoring an indirect-permission codeunit was inconvenient. Every caller now has the elevated right for every code path, not just the one the helper implements. -See sample: `samples/security/use-indirect-permissions-for-elevated-access/bad.al`. +See sample: `use-indirect-permissions-for-elevated-access.bad.al`. diff --git a/samples/security/use-inherent-permissions-to-grant-minimal-access/bad.al b/microsoft/knowledge/security/use-inherent-permissions-to-grant-minimal-access.bad.al similarity index 100% rename from samples/security/use-inherent-permissions-to-grant-minimal-access/bad.al rename to microsoft/knowledge/security/use-inherent-permissions-to-grant-minimal-access.bad.al diff --git a/samples/security/use-inherent-permissions-to-grant-minimal-access/good.al b/microsoft/knowledge/security/use-inherent-permissions-to-grant-minimal-access.good.al similarity index 100% rename from samples/security/use-inherent-permissions-to-grant-minimal-access/good.al rename to microsoft/knowledge/security/use-inherent-permissions-to-grant-minimal-access.good.al diff --git a/microsoft/knowledge/security/use-inherent-permissions-to-grant-minimal-access.md b/microsoft/knowledge/security/use-inherent-permissions-to-grant-minimal-access.md index 7103d7a..d305791 100644 --- a/microsoft/knowledge/security/use-inherent-permissions-to-grant-minimal-access.md +++ b/microsoft/knowledge/security/use-inherent-permissions-to-grant-minimal-access.md @@ -19,11 +19,11 @@ The InherentPermissions attribute attaches a minimum access grant to a procedure Annotate read-only helper procedures with InherentPermissions specifying only the tables and access letters the body uses (typically 'r'). Callers do not need direct read rights on the underlying extension-owned table, so the calling role can be narrower. This is the narrowest of the elevation options and is appropriate for read-only lookup helpers. -See sample: `samples/security/use-inherent-permissions-to-grant-minimal-access/good.al`. +See sample: `use-inherent-permissions-to-grant-minimal-access.good.al`. ## Anti Pattern A helper that reads a single lookup value but forces every calling role to hold tabledata read rights, because the helper does not declare its own inherent permissions. The broad read right then applies to every other code path that role can reach, not just the helper. -See sample: `samples/security/use-inherent-permissions-to-grant-minimal-access/bad.al`. +See sample: `use-inherent-permissions-to-grant-minimal-access.bad.al`. diff --git a/samples/security/use-isolated-storage-for-module-and-company-secrets/bad.al b/microsoft/knowledge/security/use-isolated-storage-for-module-and-company-secrets.bad.al similarity index 100% rename from samples/security/use-isolated-storage-for-module-and-company-secrets/bad.al rename to microsoft/knowledge/security/use-isolated-storage-for-module-and-company-secrets.bad.al diff --git a/samples/security/use-isolated-storage-for-module-and-company-secrets/good.al b/microsoft/knowledge/security/use-isolated-storage-for-module-and-company-secrets.good.al similarity index 100% rename from samples/security/use-isolated-storage-for-module-and-company-secrets/good.al rename to microsoft/knowledge/security/use-isolated-storage-for-module-and-company-secrets.good.al diff --git a/microsoft/knowledge/security/use-isolated-storage-for-module-and-company-secrets.md b/microsoft/knowledge/security/use-isolated-storage-for-module-and-company-secrets.md index db7aee5..78b46dc 100644 --- a/microsoft/knowledge/security/use-isolated-storage-for-module-and-company-secrets.md +++ b/microsoft/knowledge/security/use-isolated-storage-for-module-and-company-secrets.md @@ -19,11 +19,11 @@ IsolatedStorage is a per-extension, per-tenant key-value store. DataScope::Modul Use IsolatedStorage.SetEncrypted to write secrets, IsolatedStorage.Contains to probe, and IsolatedStorage.Get into a SecretText destination to read. Choose DataScope::Company for per-company credentials (for example, a tenant-per-company service account) and DataScope::Module for extension-wide configuration. -See sample: `samples/security/use-isolated-storage-for-module-and-company-secrets/good.al`. +See sample: `use-isolated-storage-for-module-and-company-secrets.good.al`. ## Anti Pattern Storing secrets in a Setup table column as plain Text, or using IsolatedStorage.Set (unencrypted) for values that authenticate the extension to an external service. Both shapes leave the secret readable by anyone with read rights on the underlying storage. -See sample: `samples/security/use-isolated-storage-for-module-and-company-secrets/bad.al`. +See sample: `use-isolated-storage-for-module-and-company-secrets.bad.al`. diff --git a/samples/security/use-nondebuggable-when-parsing-secrets/bad.al b/microsoft/knowledge/security/use-nondebuggable-when-parsing-secrets.bad.al similarity index 100% rename from samples/security/use-nondebuggable-when-parsing-secrets/bad.al rename to microsoft/knowledge/security/use-nondebuggable-when-parsing-secrets.bad.al diff --git a/samples/security/use-nondebuggable-when-parsing-secrets/good.al b/microsoft/knowledge/security/use-nondebuggable-when-parsing-secrets.good.al similarity index 100% rename from samples/security/use-nondebuggable-when-parsing-secrets/good.al rename to microsoft/knowledge/security/use-nondebuggable-when-parsing-secrets.good.al diff --git a/microsoft/knowledge/security/use-nondebuggable-when-parsing-secrets.md b/microsoft/knowledge/security/use-nondebuggable-when-parsing-secrets.md index a929de0..4389394 100644 --- a/microsoft/knowledge/security/use-nondebuggable-when-parsing-secrets.md +++ b/microsoft/knowledge/security/use-nondebuggable-when-parsing-secrets.md @@ -19,11 +19,11 @@ SecretText transit (assignment between SecretText variables, parameters, and ret Apply [NonDebuggable] to any procedure that reads a response body, parses it, and assigns the extracted secret to a SecretText out-parameter or return. Keep the procedure narrow: it SHOULD do the minimum work required to obtain the SecretText, and nothing else. -See sample: `samples/security/use-nondebuggable-when-parsing-secrets/good.al`. +See sample: `use-nondebuggable-when-parsing-secrets.good.al`. ## Anti Pattern Parsing a token response in a normal (debuggable) procedure. The plaintext token is visible in debug sessions and snapshots taken during the parse. -See sample: `samples/security/use-nondebuggable-when-parsing-secrets/bad.al`. +See sample: `use-nondebuggable-when-parsing-secrets.bad.al`. diff --git a/samples/security/use-secrettext-for-credentials/bad.al b/microsoft/knowledge/security/use-secrettext-for-credentials.bad.al similarity index 100% rename from samples/security/use-secrettext-for-credentials/bad.al rename to microsoft/knowledge/security/use-secrettext-for-credentials.bad.al diff --git a/samples/security/use-secrettext-for-credentials/good.al b/microsoft/knowledge/security/use-secrettext-for-credentials.good.al similarity index 100% rename from samples/security/use-secrettext-for-credentials/good.al rename to microsoft/knowledge/security/use-secrettext-for-credentials.good.al diff --git a/microsoft/knowledge/security/use-secrettext-for-credentials.md b/microsoft/knowledge/security/use-secrettext-for-credentials.md index a19fffd..991f2d0 100644 --- a/microsoft/knowledge/security/use-secrettext-for-credentials.md +++ b/microsoft/knowledge/security/use-secrettext-for-credentials.md @@ -19,11 +19,11 @@ SecretText is a compile-time-checked AL type for credentials, API keys, tokens, Type every credential-carrying variable, procedure parameter, and return as SecretText. Compose values with SecretStrSubstNo (see compose-secrets-with-secretstrsubstno). For HttpClient integration, see use-secrettext-with-httpclient. When a secret must be extracted from a Text source, contain that conversion in a NonDebuggable procedure (see use-nondebuggable-when-parsing-secrets). -See sample: `samples/security/use-secrettext-for-credentials/good.al`. +See sample: `use-secrettext-for-credentials.good.al`. ## Anti Pattern Passing credentials around as Text or Code parameters. Every such variable is visible in the debugger and may be captured by error handlers, logs, and telemetry that treat Text as non-sensitive. -See sample: `samples/security/use-secrettext-for-credentials/bad.al`. +See sample: `use-secrettext-for-credentials.bad.al`. diff --git a/samples/security/use-secrettext-with-httpclient/bad.al b/microsoft/knowledge/security/use-secrettext-with-httpclient.bad.al similarity index 100% rename from samples/security/use-secrettext-with-httpclient/bad.al rename to microsoft/knowledge/security/use-secrettext-with-httpclient.bad.al diff --git a/samples/security/use-secrettext-with-httpclient/good.al b/microsoft/knowledge/security/use-secrettext-with-httpclient.good.al similarity index 100% rename from samples/security/use-secrettext-with-httpclient/good.al rename to microsoft/knowledge/security/use-secrettext-with-httpclient.good.al diff --git a/microsoft/knowledge/security/use-secrettext-with-httpclient.md b/microsoft/knowledge/security/use-secrettext-with-httpclient.md index 732df3f..2acb268 100644 --- a/microsoft/knowledge/security/use-secrettext-with-httpclient.md +++ b/microsoft/knowledge/security/use-secrettext-with-httpclient.md @@ -19,11 +19,11 @@ HttpRequestMessage, HttpHeaders, and HttpContent expose SecretText overloads so Use HttpRequestMessage.SetSecretRequestUri when any URI component is sensitive (for example, a per-call API key in the path or query), and send the request with HttpClient.Send. Add Authorization headers as SecretText. Check for the presence of a secret header with ContainsSecret, not Contains. -See sample: `samples/security/use-secrettext-with-httpclient/good.al`. +See sample: `use-secrettext-with-httpclient.good.al`. ## Anti Pattern Materializing a URI or header value as Text to 'just get it to compile' — for example, StrSubstNo into a Text and then HttpClient.Get(FullUrl, Response). The resulting Text is visible in debuggers, and the URL is typically captured by platform-level logging the extension does not control. -See sample: `samples/security/use-secrettext-with-httpclient/bad.al`. +See sample: `use-secrettext-with-httpclient.bad.al`. diff --git a/samples/README.md b/samples/README.md deleted file mode 100644 index 456833c..0000000 --- a/samples/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# BCQuality Samples - -This directory contains AL (and, over time, other-language) code samples referenced by knowledge articles in BCQuality. - -## Layout - -Samples are organized by domain and by the slug of the knowledge article that references them: - -``` -samples/ - / - / - bad.al # demonstrates the anti-pattern - good.al # demonstrates the best practice -``` - -Some articles only have a `good.al` (best practice only) or only a `bad.al` (pure avoidance). That is intentional. - -## Status - -All samples are **demonstration-only**. They are self-contained AL objects with object IDs in the 50100-50299 range and are not meant to be deployed, nor are they derived from Microsoft's Business Central base application source. They exist to make the accompanying knowledge articles concrete for human readers and for agents that benefit from a worked example. - -## Referencing samples from knowledge articles - -Knowledge articles MUST NOT contain fenced code blocks (see `skills/read.md`). When a knowledge article wants to show code, it references the relevant sample by path, for example: - -> See sample: `samples/performance/filter-before-find/good.al`. - -Orchestrators and action skills are free to read these files and include relevant excerpts in their output. diff --git a/skills/read.md b/skills/read.md index 1d25ac1..96b39b9 100644 --- a/skills/read.md +++ b/skills/read.md @@ -18,7 +18,7 @@ A knowledge file is a single markdown file that covers **one concern** in Busine - A YAML frontmatter block with the fields below. All fields are required. - A `## Description` section. Required. - Optional sections — typically `## Best Practice` and `## Anti Pattern`, but any `##` section is permitted. -- No fenced code blocks. Sample code lives in separate sample files referenced by path. +- No fenced code blocks. Sample code lives in **sibling files** next to the article (see [Sample files](#sample-files) below). A file that violates any of these rules is invalid and MUST be skipped by consumers. Do not attempt to partially parse invalid files. @@ -115,6 +115,26 @@ Consumers MUST NOT silently treat missing context as a match. A consumer that produces output referencing a knowledge file MUST cite it by its repo-relative path (for example, `microsoft/knowledge/performance/filter-before-find.md`). Line numbers are not stable references; use the file path only. If a commit SHA is available to the consumer, it SHOULD be included alongside the path. +## Sample files + +Knowledge files MUST NOT contain fenced code blocks. When an article needs to show code, it ships the code as one or more **sibling files** next to the article, using this naming convention: + +``` +/knowledge//.md # the article +/knowledge//.good.al # best-practice demonstration +/knowledge//.bad.al # anti-pattern demonstration +``` + +Rules: + +- A sample file is identified by the article's slug followed by a `..` suffix. The supported kinds are `good` and `bad`. Additional kinds MAY be introduced by a layer; consumers MUST ignore unknown kinds without failing. +- The extension matches the technology (`al`, `ps1`, `js`, `kql`, …). A single article MAY carry samples in multiple technologies if the article's frontmatter `technologies` lists them. +- Articles MAY have a `good` sample only, a `bad` sample only, both, or neither. The article text SHOULD reference each sample it ships, using a relative path like `` `.good.al` ``. +- Samples are **demonstration-only**. They are not deployed, and they are not derived from the Business Central base application source. Authors use object IDs they own (e.g., 50100–50999 in Microsoft-authored samples) and keep each sample self-contained. +- Layer precedence applies to sample files the same way it applies to articles: a `/custom/knowledge//.good.al` overrides a `/microsoft/knowledge//.good.al` for the same article in the same layer hierarchy. + +Consumers that surface sample code to an end user or agent SHOULD cite the sample file by its repo-relative path, in the same format as article citations. + ## Retrieval workflow The standard workflow for finding applicable files: diff --git a/skills/write.md b/skills/write.md index 998e286..22af773 100644 --- a/skills/write.md +++ b/skills/write.md @@ -39,7 +39,7 @@ Custom `##` sections are permitted when they serve the concern (for example, `## ## No fenced code blocks -Knowledge files do not contain code. Samples live in separate files under `/samples/` (or the layer's sample folder) and are referenced by path. This keeps knowledge files retrieval-friendly and prevents code from drifting out of sync with BC platform changes buried inside prose. +Knowledge files do not contain code. Samples live as **sibling files** next to the article — `.good.al`, `.bad.al`, etc. — in the same knowledge-layer folder. See `skills/read.md` for the full convention. This keeps knowledge files retrieval-friendly and prevents code from drifting out of sync with BC platform changes buried inside prose. ## Choosing frontmatter values