Drop the two new KB articles from this PR

The Execution-discipline change + suggested-code propagation in the
skills is the structural fix. The two knowledge articles
(case-must-handle-unknown-enum-values, instream-length-unreliable-for-bc-streams)
were T4 follow-ups derived from a single parity case study; they need
broader review before landing as canonical BCQuality knowledge and are
out of scope for this PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Jesper Schulz-Wedde 2026-05-28 10:57:23 +02:00
parent 31b9949235
commit 539be9d735
6 changed files with 0 additions and 202 deletions

View file

@ -1,29 +0,0 @@
codeunit 50272 "Sample InStream Length Bad"
{
var
MaxSimpleUploadSize: Integer;
procedure Upload(var Stream: InStream; FileName: Text)
var
SimpleResp: HttpResponseMessage;
ChunkedResp: HttpResponseMessage;
begin
MaxSimpleUploadSize := 4 * 1024 * 1024;
// Wrong: Stream.Length is 0 or unreliable for streams from HTTP
// responses, some file APIs, and caller-supplied streams.
if Stream.Length <= MaxSimpleUploadSize then
UploadSimple(Stream, FileName, SimpleResp)
else
UploadChunked(Stream, FileName, ChunkedResp);
end;
local procedure UploadSimple(var Stream: InStream; FileName: Text; var Response: HttpResponseMessage)
begin
// ... PUT to /items/{id}/content endpoint
end;
local procedure UploadChunked(var Stream: InStream; FileName: Text; var Response: HttpResponseMessage)
begin
// ... POST to /items/{id}/createUploadSession endpoint, then PUT in chunks
end;
}

View file

@ -1,35 +0,0 @@
codeunit 50273 "Sample InStream Length Good"
{
var
MaxSimpleUploadSize: Integer;
procedure Upload(var Stream: InStream; FileName: Text)
var
TempBlob: Codeunit "Temp Blob";
SizedStream: InStream;
BufferLength: Integer;
SimpleResp: HttpResponseMessage;
ChunkedResp: HttpResponseMessage;
begin
MaxSimpleUploadSize := 4 * 1024 * 1024;
// Materialise once into a Temp Blob; its length is reliable.
CopyStream(TempBlob.CreateOutStream(), Stream);
BufferLength := TempBlob.Length();
TempBlob.CreateInStream(SizedStream);
if (BufferLength > 0) and (BufferLength <= MaxSimpleUploadSize) then
UploadSimple(SizedStream, FileName, SimpleResp)
else
UploadChunked(SizedStream, FileName, ChunkedResp);
end;
local procedure UploadSimple(var Stream: InStream; FileName: Text; var Response: HttpResponseMessage)
begin
// ... PUT to /items/{id}/content endpoint
end;
local procedure UploadChunked(var Stream: InStream; FileName: Text; var Response: HttpResponseMessage)
begin
// ... POST to /items/{id}/createUploadSession endpoint, then PUT in chunks
end;
}

View file

@ -1,48 +0,0 @@
---
bc-version: [all]
domain: performance
keywords: [instream, outstream, length, stream, upload, blob, http, chunk]
technologies: [al]
countries: [w1]
application-area: [all]
---
# `InStream.Length` is unreliable for branching on payload size
## Description
`InStream` exposes a `Length` property that returns the total byte count of the underlying buffer when the runtime can determine it. The catch is that "when the runtime can determine it" depends on **how the stream was obtained**:
- Streams produced from a `Blob` or a `Temp Blob` field by `CreateInStream``Length` is reliable; the blob is fully materialised.
- Streams produced from a Media or MediaSet field — same: backed by a known-size payload.
- Streams produced from `HttpResponseMessage.Content.ReadAs` and from many `File.*` APIs — `Length` may return `0` or a partial value, because the underlying transport is consumed incrementally and the total length is not known until the stream is exhausted.
- Streams from `Stream` parameters supplied by callers — depends entirely on what the caller passed in.
Branching upload behaviour on `Stream.Length` is the common failure mode. The pattern is:
```al
if Stream.Length <= MaxSimpleUploadSize then
UploadSimple(Stream)
else
UploadChunked(Stream);
```
For a Microsoft Graph drive upload, `MaxSimpleUploadSize` is 4 MB. If `Stream.Length` returns `0` (because the stream came from an HTTP response or a freshly written outstream that the runtime cannot size cheaply), the code takes the simple-upload path with a 10 MB file behind it, the API returns `413 Payload Too Large`, and the upload fails. The error surfaces to the user as a generic HTTP failure with no obvious connection to the buggy size check.
The same trap applies to any code that "skips the work if the stream is empty": `if Stream.Length = 0 then exit;` silently drops payloads when the stream came from a source that does not pre-compute length.
## Best Practice
Decide which behaviour you actually need.
- **Always-chunked** is the safe default when the stream's origin is not under your control. Chunked uploads work for any payload size; the per-chunk overhead is small for small payloads.
- When a size threshold is genuinely required (for example, choosing between two endpoints with different cost profiles), copy the stream into a known-size buffer first — typically a `Temp Blob` — and read length from the blob, which IS reliable. The cost of one round-trip through a blob is acceptable for the upload-routing decision.
- When the threshold is informational (logging, telemetry), guard against `0`: `if (Stream.Length > 0) and (Stream.Length <= Threshold)` so an unknown size routes to the safe path, not the optimistic one.
See sample: `instream-length-unreliable-for-bc-streams.good.al`.
## Anti Pattern
Branching upload size, validation, or buffer allocation directly on `Stream.Length` when the stream's origin is anything other than a freshly-materialised blob. Detection signal: `Stream.Length` (or `.Length` on a variable typed `InStream` or `OutStream`) appearing as the left or right side of `<=`, `<`, `>=`, `>`, or `=` against a size-like constant or `Label`, with no prior copy through a `Blob`. The narrower signal — branching simple-vs-chunked upload on `Length` for Graph or REST endpoints with a documented size cap — is the high-confidence case.
See sample: `instream-length-unreliable-for-bc-streams.bad.al`.