From d2e2f2adb4f4f7734696a882618d5527fdeb29b9 Mon Sep 17 00:00:00 2001 From: Michael Dieringer <65093775+MichaelDieringer@users.noreply.github.com> Date: Fri, 3 Jul 2026 19:03:23 +0200 Subject: [PATCH] Fredagsregler: tre evidensbaserede tilfoejelser fra ugens haendelser 1. text-artifacts-require-explicit-utf8-handling (Type B): to produktionshaendelser, samme sygdom - smiley double-encodet via HTTP-streng, og en Mode B-batch der skrev mojibake i tre CLAUDE.md fordi PS 5.1 laeser BOM-loese .ps1 som cp1252. Reglen: raa bytes ved transfer, eksplicit UTF-8 ved write, tekst-transformation i Python, grep for maerket foer commit. 2. log-writes-must-survive-rollback (Type B): fejl-logs skrevet i samme transaktion forsvinder ved rollback - loggen mister praecis de fejl den findes for. Isoleret session (StartSession -> insert + commit) er moensteret. Evidens: Wareco IC web-service-log 2026-07-02; havde tidligere kostet en udvikler det meste af en dag. 3. Skaerpelse af setup-doc-must-not-reference-unpromoted-stable-files: kanal-tilstand verificeres via git (ls-remote/klon), aldrig CDN; to sande observationer kan modsige hinanden naar verden flytter sig imellem dem - tidsstempl al kanal-evidens. Felt-verificeret af Conzept-sessionens selvkorrektion 2026-07-03. Foerste PR foedt direkte i QualityHub. Co-Authored-By: Claude Fable 5 --- .../log-writes-must-survive-rollback.md | 64 ++++++++++++++++++ ...t-not-reference-unpromoted-stable-files.md | 7 ++ ...rtifacts-require-explicit-utf8-handling.md | 66 +++++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 custom/knowledge/architecture/log-writes-must-survive-rollback.md create mode 100644 custom/knowledge/architecture/text-artifacts-require-explicit-utf8-handling.md diff --git a/custom/knowledge/architecture/log-writes-must-survive-rollback.md b/custom/knowledge/architecture/log-writes-must-survive-rollback.md new file mode 100644 index 0000000..32d4a47 --- /dev/null +++ b/custom/knowledge/architecture/log-writes-must-survive-rollback.md @@ -0,0 +1,64 @@ +--- +bc-version: [all] +domain: architecture +keywords: [logging, rollback, session, transaction, error-log, web-service, telemetry] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Log writes must survive transaction rollback + +## Description + +The naive instinct when adding logging is to insert the log record inside +the running transaction. That log has a fatal blind spot: when the operation +ERRORS, the transaction rolls back — and takes the log entry with it. The +result is a log that faithfully records every success and silently loses +exactly the failures it exists to capture. + +Observed in production design (Wareco IC, 2026-07-02): a log of errors and +duration per web-service call needed entries to persist even when the +business transaction was rolled back. The correct pattern — writing the log +from an ISOLATED SESSION so its commit is independent of the caller's +transaction — had previously cost a developer most of a day to discover. + +## Rule + +A log whose purpose includes capturing FAILURES must write its entries in a +transaction that is independent of the operation being logged: + +1. **Isolated session write:** start a separate session (`StartSession` on a + codeunit that inserts the log record and commits) so the entry persists + regardless of what happens to the caller's transaction. +2. The log codeunit does ONE thing: insert + implicit commit. No business + logic rides along in the isolated session — anything committed there + escapes the caller's rollback by design. +3. Duration/telemetry fields are captured in the caller and passed as + parameters — the isolated session must not re-read state that the + rollback may erase. +4. Logs that only record successes (audit of committed work) may stay in the + main transaction — this rule targets error/diagnostic logs specifically. + +## What NOT to do + +- Do not `Insert` the error-log record in the same transaction as the + operation — the error you most need to see is the one that deletes it +- Do not "fix" it with `Commit` before the risky call — a stray `Commit` + breaks the caller's atomicity and violates posting-routine rules +- Do not swallow errors to keep the log alive (`if Codeunit.Run then...` + without re-raising) — the log must observe the failure, not suppress it + +## Signal to watch for + +A log table whose entries are inserted in the flow they measure, with no +isolated-session writer — combined with any error path. In review: search +callers of the log-insert for surrounding `Insert`/`Post` in the same +transaction scope. + +## Message to developer + +When an in-transaction error log is found, explain: entries vanish on +rollback, so the log is blind to failures; route the write through an +isolated session (StartSession → insert+commit codeunit) with values passed +as parameters. diff --git a/custom/knowledge/architecture/setup-doc-must-not-reference-unpromoted-stable-files.md b/custom/knowledge/architecture/setup-doc-must-not-reference-unpromoted-stable-files.md index bda3cb6..aa6743d 100644 --- a/custom/knowledge/architecture/setup-doc-must-not-reference-unpromoted-stable-files.md +++ b/custom/knowledge/architecture/setup-doc-must-not-reference-unpromoted-stable-files.md @@ -41,6 +41,13 @@ sit merged on `main` without the referenced file being available on 3. Consumers (sessions, Mode B) that hit a 404 on a documented stable path report the gap — they never silently fetch from `main` instead. The channel discipline outranks the convenience. +4. **Channel-state evidence comes from git, never from a CDN.** Verify with + `git ls-remote`/a fresh clone — raw-URL caches lag minutes behind, so a + 404/200 observed right after a promote can be stale. Two true + observations can contradict each other when the world moves between + them: timestamp every piece of channel evidence before acting on it. + (Field-verified 2026-07-03: a session's real 404 was fixed mid-flight; + it re-verified by cloning the branch and correctly withdrew its case.) ## What NOT to do diff --git a/custom/knowledge/architecture/text-artifacts-require-explicit-utf8-handling.md b/custom/knowledge/architecture/text-artifacts-require-explicit-utf8-handling.md new file mode 100644 index 0000000..885c2a6 --- /dev/null +++ b/custom/knowledge/architecture/text-artifacts-require-explicit-utf8-handling.md @@ -0,0 +1,66 @@ +--- +bc-version: [all] +domain: architecture +keywords: [encoding, utf-8, powershell, mojibake, here-string, bom, raw-bytes] +technologies: [al] +countries: [w1] +application-area: [all] +--- + +# Text artifacts require explicit UTF-8 handling — no ambient encoding + +## Description + +Two production incidents, same disease, different vectors. (1) +`smiley.agent.md` shipped double-UTF-8-encoded for weeks: fetched as string +content over HTTP and re-saved with ambient encoding — every em-dash became +`â€"`, and `file` misclassified the agent as Nim source code. (2) A Mode B +batch script wrote the SAME mojibake into three project CLAUDE.md files: +Windows PowerShell 5.1 reads a BOM-less `.ps1` as cp1252, so UTF-8 em-dashes +inside a here-string were mis-decoded before they were ever written. Both +incidents corrupted deployed instruction files silently; both were caught by +inspection, not by tooling. + +The root cause is never the characters — it is trusting an AMBIENT encoding +(HTTP string decoding, PS 5.1 script parsing, `Out-File` defaults) anywhere +between a text artifact's source and its destination. + +## Rule + +When creating, fetching or transforming CURABIS text artifacts (agent files, +CLAUDE.md, knowledge files, scripts): + +1. **Transfers are raw bytes.** Filesystem `Copy-Item` or + `Invoke-WebRequest -OutFile` — never via `.Content` strings. +2. **Writes are explicit UTF-8.** `[System.IO.File]::WriteAllText(path, + text, [System.Text.UTF8Encoding]::new($false))` — never `Out-File` / + `Set-Content` defaults for content with non-ASCII. +3. **Text transformation with non-ASCII happens in Python** (explicit + `encoding="utf-8"` on read AND write) — or, if it must be PowerShell, the + `.ps1` file itself carries a UTF-8 BOM so PS 5.1 parses its literals + correctly. A BOM-less `.ps1` with non-ASCII literals is a latent bug. +4. **Verify after generation:** grep the output for `â€` (the mojibake + signature) before committing or deploying. One line, catches the class. + +## What NOT to do + +- Do not put em-dashes, æøå or any non-ASCII inside a here-string in a + `.ps1` that lacks a BOM — the corruption happens at parse time, before + your code runs +- Do not fetch a file via `.Content` and re-save it — the double-decode is + invisible until someone reads the artifact +- Do not assume "it looked fine in my editor" — editors auto-detect; parsers + and runtimes do not + +## Signal to watch for + +The literal byte sequence `â€` (or `æ`, `ø`, `Ã¥`) in any committed or +deployed text file. Also: `file`/tooling misclassifying a markdown file as +source code — that is encoding damage until proven otherwise. + +## Message to developer + +When mojibake is found in an artifact, report which file, repair by +cp1252→UTF-8 reversal (line-by-line fallback preserves already-correct +lines), and identify WHICH transfer step used ambient encoding — the repair +without the root cause just schedules the next incident.