Triage seed knowledge and document admission test for preview

Remove seven knowledge files whose content is generic software-engineering
guidance that a capable LLM already applies without BCQuality present
(HTTPS-only, secret-leakage-in-errors, no-credentials-in-URLs, silent
security-error swallowing, short transaction scope, HTTP timeouts,
StrSubstNo-vs-concatenation). These fail the remedial-knowledge premise
and dilute the signal of the preview corpus.

Strip the "Seed article — domain stewards should expand" banner from ten
files that are ready to showcase (AA0232/AA0233 rules, FindSet read-only
semantics, SetLoadFields ordering and usage, CalcFields-in-loops,
SecretText end-to-end, DataClassification). The banner remains on files
that still need domain-steward refinement.

Add a "What belongs here" section to the README stating the admission
test: a file exists only if a modern LLM would get something wrong or
miss something without it. Gives contributors a concrete yes/no filter
before they open a PR.
This commit is contained in:
Jesper Schulz-Wedde 2026-04-23 15:47:01 +02:00
parent 5a02e6ec93
commit 23184480d0
32 changed files with 14 additions and 424 deletions

View file

@ -9,8 +9,6 @@ application-area: [all]
# Add SIFT keys for FlowField aggregations
> **Seed article.** Converted from an existing performance-review prompt to bootstrap the BCQuality performance corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
CodeCop rule AA0232 checks that FlowFields backed by CalcSums or aggregation CalcFormula are supported by a key whose SumIndexFields include the summed field and whose key prefix matches the formula's filter fields. Without a SIFT key the platform falls back to a full aggregation on every read — typically invisible in development and catastrophic in production.

View file

@ -9,8 +9,6 @@ application-area: [all]
# Do not call CalcFields inside loops
> **Seed article.** Converted from an existing performance-review prompt to bootstrap the BCQuality performance corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
CalcFields evaluates one or more FlowFields for the current record by issuing a separate SQL aggregation. Called inside a loop over a record set, it becomes an N+1 problem: one aggregate per row. For any non-trivial set on a ledger-entry-backed FlowField this is orders of magnitude slower than the equivalent batched query.

View file

@ -9,8 +9,6 @@ application-area: [all]
# Do not pair FindFirst, FindLast, or Get with Next
> **Seed article.** Converted from an existing performance-review prompt to bootstrap the BCQuality performance corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
CodeCop rule AA0233 flags loops that start with FindFirst, FindLast, or Get and then call Next. FindFirst and FindLast retrieve a single row and reposition the cursor; calling Next after them forces the platform to re-seek and stream the rest of the set, which is slower than the correct FindSet pattern and signals intent incorrectly to reviewers and the optimizer.

View file

@ -9,8 +9,6 @@ application-area: [all]
# Filter before you find
> **Seed article.** Converted from an existing performance-review prompt to bootstrap the BCQuality performance corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
Every call to FindSet, Find, or FindFirst on an unfiltered record variable scans the entire table. On hot tables (ledger entries, value entries, sales invoice lines) a production dataset can easily be millions of rows, so the cost of forgetting a filter is orders of magnitude worse than the cost of applying one.

View file

@ -1,18 +0,0 @@
codeunit 50128 "Perf Sample TxnScope Bad"
{
procedure ImportCustomers(var Source: List of [Text])
var
Customer: Record Customer;
HttpClient: HttpClient;
HttpResponse: HttpResponseMessage;
Row: Text;
begin
foreach Row in Source do begin
// external call inside the write transaction
HttpClient.Get('https://example.com/validate?row=' + Row, HttpResponse);
Customer.Init();
// ... populate from Row ...
Customer.Insert(true);
end;
end;
}

View file

@ -1,22 +0,0 @@
codeunit 50123 "Perf Sample TxnScope Good"
{
procedure ImportCustomers(var Source: List of [Text])
var
Prepared: Record Customer temporary;
Customer: Record Customer;
begin
// read, validate, and shape outside the transaction
PrepareRows(Source, Prepared);
// transaction starts here: only Insert/Modify calls
if Prepared.FindSet() then
repeat
Customer := Prepared;
Customer.Insert(true);
until Prepared.Next() = 0;
end;
local procedure PrepareRows(var Source: List of [Text]; var Prepared: Record Customer temporary)
begin
end;
}

View file

@ -1,29 +0,0 @@
---
bc-version: [26..28]
domain: performance
keywords: [transaction, lock, scope, contention]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Keep transaction scope short
> **Seed article.** Converted from an existing performance-review prompt to bootstrap the BCQuality performance corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
Every write operation runs inside a transaction that holds locks until the transaction ends. Long transactions increase blocking, deadlocks, and timeouts for other sessions. The same work split across narrower transactions typically completes faster under load because it holds locks for less time.
## Best Practice
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: `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: `keep-transaction-scope-short.bad.al`.

View file

@ -9,8 +9,6 @@ application-area: [all]
# Use FindSet in read-only mode by default
> **Seed article.** Converted from an existing performance-review prompt to bootstrap the BCQuality performance corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
FindSet has two modes: FindSet() and FindSet(false) are read-only and take no write lock; FindSet(true) calls LockTable before fetching. Write locks are expensive and hold for the remainder of the transaction, so passing `true` when you do not intend to modify the records increases contention under load.

View file

@ -9,8 +9,6 @@ application-area: [all]
# Use SetLoadFields for partial records
> **Seed article.** Converted from an existing performance-review prompt to bootstrap the BCQuality performance corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
SetLoadFields instructs the platform to hydrate only the listed fields on a record variable. On wide tables, or tables with BLOB or media fields, the difference is substantial: a Sales Invoice Line has dozens of fields and loading all of them for every row of a large set is wasted bandwidth. Primary key fields, SystemId, and system audit fields are always loaded automatically. SetLoadFields works only with FieldClass = Normal; FlowFields and FlowFilters cannot be partial-loaded.

View file

@ -1,7 +0,0 @@
codeunit 50137 "Perf Sample StrSubstNo Bad"
{
procedure CustomerGreeting(var Customer: Record Customer): Text
begin
exit('Hello, ' + Customer.Name + ' (' + Customer."No." + ')');
end;
}

View file

@ -1,9 +0,0 @@
codeunit 50136 "Perf Sample StrSubstNo Good"
{
procedure CustomerGreeting(var Customer: Record Customer): Text
var
GreetingLbl: Label 'Hello, %1 (%2)';
begin
exit(StrSubstNo(GreetingLbl, Customer.Name, Customer."No."));
end;
}

View file

@ -1,29 +0,0 @@
---
bc-version: [26..28]
domain: performance
keywords: [strsubstno, string, concatenation, format]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Use StrSubstNo for message formatting
> **Seed article.** Converted from an existing performance-review prompt to bootstrap the BCQuality performance corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
StrSubstNo formats values into a placeholder template in a single call. Manual concatenation with `+` produces a chain of intermediate strings, each allocated and discarded, and mixes formatting rules inconsistently across locales. The performance difference per call is small; repeated inside a tight loop it is noticeable.
## Best Practice
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: `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: `use-strsubstno-for-message-formatting.bad.al`.

View file

@ -1,14 +0,0 @@
codeunit 50225 "Sec Sample ErrorDisclosure Bad"
{
procedure Connect()
begin
if not TryConnect() then
Error('Failed to connect to Server=PROD-SQL01;Database=NAV;User=svc_admin: %1', GetLastErrorText());
end;
[TryFunction]
local procedure TryConnect()
begin
// ...
end;
}

View file

@ -1,24 +0,0 @@
codeunit 50224 "Sec Sample ErrorDisclosure Good"
{
var
ConnectionFailedErr: Label 'Connection to the external service failed. Contact your administrator.';
procedure Connect()
begin
if not TryConnect() then begin
LogConnectionFailure(GetLastErrorText());
Error(ConnectionFailedErr);
end;
end;
[TryFunction]
local procedure TryConnect()
begin
// ...
end;
local procedure LogConnectionFailure(Detail: Text)
begin
// Route to controlled logging (Session.LogMessage, activity log, etc.).
end;
}

View file

@ -1,29 +0,0 @@
---
bc-version: [26..28]
domain: security
keywords: [error, disclosure, logging, label]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Avoid sensitive data in error messages
> **Seed article.** Converted from an existing security-review prompt to bootstrap the BCQuality security corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
Errors surfaced to end users are routinely forwarded to support systems, captured in bug reports, and exported to telemetry. Server names, database names, usernames, connection strings, file paths, and stack excerpts in an end-user error message leak infrastructure detail to untrusted consumers and help an attacker map the environment.
## Best Practice
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: `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: `avoid-sensitive-data-in-error-messages.bad.al`.

View file

@ -1,10 +0,0 @@
codeunit 50223 "Sec Sample UrlCreds Bad"
{
procedure Call(ApiKey: Text)
var
Client: HttpClient;
Response: HttpResponseMessage;
begin
Client.Get('https://api.example.com/v1/items?api_key=' + ApiKey, Response);
end;
}

View file

@ -1,13 +0,0 @@
codeunit 50222 "Sec Sample UrlCreds Good"
{
procedure Call(ApiKey: SecretText)
var
Client: HttpClient;
Response: HttpResponseMessage;
AuthHeader: SecretText;
begin
AuthHeader := SecretStrSubstNo('Bearer %1', ApiKey);
Client.DefaultRequestHeaders.Add('Authorization', AuthHeader);
Client.Get('https://api.example.com/v1/items', Response);
end;
}

View file

@ -1,29 +0,0 @@
---
bc-version: [26..28]
domain: security
keywords: [url, query-string, credentials, logging]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Do not put credentials in URLs
> **Seed article.** Converted from an existing security-review prompt to bootstrap the BCQuality security corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
URL query strings and path segments are routinely captured in web-server access logs, browser history, proxy logs, platform telemetry, and exception traces. A credential placed anywhere in the URL therefore persists across systems the extension does not control, and is typically retained far longer than the secret's intended lifetime.
## Best Practice
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: `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: `do-not-put-credentials-in-urls.bad.al`.

View file

@ -1,15 +0,0 @@
codeunit 50227 "Sec Sample SwallowErr Bad"
{
procedure Authenticate(): Boolean
begin
if not TryAuthenticate() then
exit(false);
exit(true);
end;
[TryFunction]
local procedure TryAuthenticate()
begin
// ...
end;
}

View file

@ -1,24 +0,0 @@
codeunit 50226 "Sec Sample SwallowErr Good"
{
procedure Authenticate(): Boolean
begin
if TryAuthenticate() then
exit(true);
LogAuthFailure(GetLastErrorText());
exit(false);
end;
[TryFunction]
local procedure TryAuthenticate()
begin
// ...
end;
local procedure LogAuthFailure(Detail: Text)
begin
Session.LogMessage('SEC0001', 'Authentication failed', Verbosity::Warning,
DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher,
'Detail', Detail);
end;
}

View file

@ -1,29 +0,0 @@
---
bc-version: [26..28]
domain: security
keywords: [tryfunction, logging, audit, error]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Do not swallow security errors silently
> **Seed article.** Converted from an existing security-review prompt to bootstrap the BCQuality security corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
Authentication failures, permission denials, and unexpected error paths in security-relevant code are the signals a reviewer or incident responder needs to see. A TryFunction whose failure is ignored without logging turns an attack or a misconfiguration into silent bad behaviour: the call returns false, the caller moves on, and no record of the event survives.
## Best Practice
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: `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: `do-not-swallow-security-errors-silently.bad.al`.

View file

@ -1,10 +0,0 @@
codeunit 50219 "Sec Sample Https Bad"
{
procedure CallExternal()
var
Client: HttpClient;
Response: HttpResponseMessage;
begin
Client.Get('http://api.example.com/data', Response);
end;
}

View file

@ -1,12 +0,0 @@
codeunit 50218 "Sec Sample Https Good"
{
procedure CallExternal(Endpoint: Text)
var
Client: HttpClient;
Response: HttpResponseMessage;
begin
if not Endpoint.StartsWith('https://') then
Error('Only HTTPS endpoints are allowed.');
Client.Get(Endpoint, Response);
end;
}

View file

@ -1,29 +0,0 @@
---
bc-version: [26..28]
domain: security
keywords: [https, httpclient, tls, plaintext]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Require HTTPS for external calls
> **Seed article.** Converted from an existing security-review prompt to bootstrap the BCQuality security corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
HttpClient can issue requests over plaintext HTTP as easily as over HTTPS. A request sent over http:// is transmitted unencrypted, exposing the full URL (including query string), the request headers (including Authorization), and the bodies of both request and response to any on-path observer. This holds even when the payload itself is not marked sensitive — request signatures and session tokens are routinely captured and replayed.
## Best Practice
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: `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: `require-https-for-external-calls.bad.al`.

View file

@ -1,11 +0,0 @@
codeunit 50221 "Sec Sample Timeout Bad"
{
procedure CallExternal()
var
Client: HttpClient;
Response: HttpResponseMessage;
begin
// No Timeout set; a hung endpoint stalls the caller.
Client.Get('https://api.example.com/data', Response);
end;
}

View file

@ -1,12 +0,0 @@
codeunit 50220 "Sec Sample Timeout Good"
{
procedure CallExternal()
var
Client: HttpClient;
Response: HttpResponseMessage;
begin
Client.Timeout := 10000; // 10 seconds
if not Client.Get('https://api.example.com/data', Response) then
Error('External service is unavailable.');
end;
}

View file

@ -1,29 +0,0 @@
---
bc-version: [26..28]
domain: security
keywords: [timeout, httpclient, availability, dos]
technologies: [al]
countries: [w1]
application-area: [all]
---
# Set timeouts for external calls
> **Seed article.** Converted from an existing security-review prompt to bootstrap the BCQuality security corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
An HttpClient with no explicit timeout relies on defaults that may be long enough for a hung or slow endpoint to block a user session or a background task for minutes. A dependency that degrades therefore degrades the caller, and an intentionally slow endpoint is a cheap denial-of-service vector against the extension.
## Best Practice
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: `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: `set-timeouts-for-external-calls.bad.al`.

View file

@ -9,8 +9,6 @@ application-area: [all]
# Use SecretText for credentials
> **Seed article.** Converted from an existing security-review prompt to bootstrap the BCQuality security corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
SecretText is a compile-time-checked AL type for credentials, API keys, tokens, and similar sensitive values. The compiler rejects literal assignments to SecretText and blocks implicit conversion back to Text or Code, which prevents many accidental disclosures via logs, errors, and the debugger (regular and snapshot). A SecretText value remains opaque throughout its lifetime.

View file

@ -9,8 +9,6 @@ application-area: [all]
# Use SecretText with HttpClient
> **Seed article.** Converted from an existing security-review prompt to bootstrap the BCQuality security corpus. Domain stewards should expand, restructure, and refine as needed.
## Description
HttpRequestMessage, HttpHeaders, and HttpContent expose SecretText overloads so credentials never have to be converted back to Text to be sent. Key APIs: HttpRequestMessage.SetSecretRequestUri (for URIs containing secrets), HttpHeaders.Add(name, SecretText) for authorization headers, HttpHeaders.ContainsSecret to probe secret-valued headers, HttpContent.WriteFrom(SecretText) for request bodies, and HttpContent.ReadAs(SecretText) to pull response bodies into a secret destination.