From 353f6a42ca6a2c1cc691b505edc50b8caadeddcd Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 7 Aug 2026 01:46:56 -0500 Subject: [PATCH 1/2] backlog: ship the ASCQM catalogue pass, file #1089-#1093, mark the 5055 decline in CLAUDE.md (closes #1073) The BACKLOG #1073 pass ran over all 74 live ASCQM 1.1 elements with an adversarial refutation stage on every non-not-applicable verdict. The measure stays declined; the catalogue earned its keep on two narrow grounds and this commit records both. FINDINGS FILED #1089 HL7 parse_path accepts component 0, so PID-5.0 reads AND OVERWRITES the last component. Reproduced by execution. The X12 twin validates and is tested; the HL7 side -- the default content type, the one carrying PHI -- has neither. #1090 write_reference_snapshot's json.dumps has no default hook; the FILE reference source does not coerce where its DATABASE sibling does. Every existing test uses CSV, so the suite cannot reach it. #1091 Credential detection reads neither .ps1 nor .yaml, and the entropy gate floors out on low-entropy secrets. Written in the conditional: this is a control that cannot see the class, NOT a live exposure. #1092 Eight verdicts flipped under measurement and ALL EIGHT flipped covered->gap. Three falsify a specific written claim, including the quality record's "import/layer rules are machine-checked in CI". #1093 Inventory of the rest, with the ~19 metric findings explicitly NOT to be filed (section 4.1: a count is not an item) and two unverified premises flagged. THE DECLINE MARKER IS THE PART THAT OUTLIVES THE ITEM. CLAUDE.md section 12 now carries it. A decline recorded only in a backlog item disappears when that item archives -- verified: #26 and #27 are both in BACKLOG-CLOSED.md today and survive as binding decisions only because their markers were lifted into section 12. COUNTS RESOLVED: the CISQ-vs-ASCQM conflict was a UNITS problem. CISQ counts CWEs including children; ASCQM counts elements. Performance Efficiency = 15 is now confirmed and its unverified mark is lifted; the "139 total" stays unconfirmed and that mark stays. RECORDED AGAINST MYSELF: the first run silently judged 62 of 74 elements after one triage batch died, and returned a confident report that gave no sign a sixth of the catalogue was unread. Caught by arithmetic, not by the run. One of the missed elements became a filed finding, so it was not harmless. It is this repo's own section 4.0 failure mode reproduced inside the tool built to hunt for it. VERIFIED: backlog_status_check.py exit 0, 350 items each declaring one status; five new headings, one banner each; zero prose glyphs in the new section. NOT RUN: pytest, mypy -- no .venv in this worktree. Docs-only diff. --- CLAUDE.md | 10 +++++ docs/BACKLOG.md | 108 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 117 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6104b282..b8be4d00 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -559,6 +559,16 @@ harness process only.) - Don't build **Serial (RS-232) / ASTM E1381/E1394/E1318** lab-instrument connectivity — **declined-by-design (v0.2+)**: no real feed demand, outside the HL7/FHIR/X12/DICOM scope ([`docs/BACKLOG.md`](docs/BACKLOG.md) #27, [`docs/CONNECTIONS.md`](docs/CONNECTIONS.md)). +- Don't adopt **ISO/IEC 5055:2021 / OMG ASCQM** as a quality **measure** — **declined-by-design + (2026-08-07)**, three reasons each independently sufficient: no free or open-source + 5055-conformant **Python** analyser exists (the conformant ecosystem is C/C++/Java/C#/COBOL- + weighted), there is no contract counterparty for the clause the standard exists to support (it is + written into development and outsourcing contracts; this is OSS on PyPI), and a weakness-**count** + score collides with the anti-metric rule in + [`docs/Code_Quality_Standards.md`](docs/Code_Quality_Standards.md) §4.1. **The catalogue is a + different question and was adopted:** the ASCQM 1.1 weakness list is free from OMG, one bounded + pass over it ran under [`docs/BACKLOG.md`](docs/BACKLOG.md) #1073, and its findings are #1089–#1093. + Re-running that pass is legitimate; adopting the score is not. - Don't keep grinding in a polluted context — `/clear` after repeated failures. - Don't use **glyphs or emoji** in prose, comments, commit messages, PR bodies or replies — say the word (§11). The backlog status-banner alphabet is the one machine-parsed holdout; read it with diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index f238881b..5cc94c0c 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -5676,7 +5676,11 @@ environments.py:79 `if not base_dir: return cwd` <-- and base_dir i ## 1073. Mine the free ASCQM 1.1 weakness catalogue against the existing gates; decline ISO 5055 as a measure -> 🔢 **Filed 2026-08-06 — not started.** Value **4/10** · Difficulty **3/10**. ISO/IEC 5055:2021 defines four quality measures as **counts** of CWE-keyed severe weaknesses. The **measure** is declined for the reasons below and should not be re-litigated. The **catalogue** behind it is free, curated by a standards body, and contains a slice worth one bounded pass: the system-level weaknesses that a unit-level linter structurally cannot see. +> ✅ **SHIPPED 2026-08-07 — the pass ran over all 74 live elements; the measure stays declined.** Value **4/10** · Difficulty **3/10**. Findings filed as #1089, #1090, #1091, #1092 and the #1093 inventory. The decline marker now sits in [`../CLAUDE.md`](../CLAUDE.md) §12, which is the part that outlives this item — a decline recorded only here would vanish when this item archives, exactly as #26 and #27 would have. Original filing follows. ISO/IEC 5055:2021 defines four quality measures as **counts** of CWE-keyed severe weaknesses. The **measure** is declined for the reasons below and should not be re-litigated. The **catalogue** behind it is free, curated by a standards body, and contains a slice worth one bounded pass: the system-level weaknesses that a unit-level linter structurally cannot see. + +**THE COUNTS ARE RESOLVED, and the conflict was a UNITS problem nobody had named.** CISQ's 74 / 74 / 29 / 15 counts **CWEs**, including contributing child CWEs. ASCQM's 22 / 29 / 15 / 20 counts **elements**, and one element carries several CWEs — which is why the element count is roughly a third of the CWE count. Measured from the spec: **84 elements, 74 live, 10 marked Dropped by the standard itself.** Security 22, Performance Efficiency 15 and Maintainability 20 reconcile **exactly**; Reliability came to 27 against 29 expected, and `ASCRM-RLB-13` carries no CWE mapping — both are **known shortfalls, not resolved**. **Performance Efficiency = 15 is now CONFIRMED** from the spec and its unverified mark is lifted. **The "139 total" stays UNCONFIRMED**: 152 distinct CWE references appear across the 261 pages, but that is a mention count over the whole document including front matter, so it neither confirms nor refutes 139. That mark stays, and it is doing its job. + +**THE FIRST RUN SILENTLY EXAMINED 62 OF 74 ELEMENTS, AND THE RESULT LOOKED COMPLETE.** One of six triage batches died on a connection error. The surviving five returned a confident report with a headline gap count, and **nothing in it indicated that a sixth of the catalogue had never been read.** The 12 unexamined elements spanned all four measures and included three Security elements (CWE-99, CWE-456, CWE-789) — and CWE-456 became a filed finding (#1093) once actually judged, so the omission was **not** harmless. It was caught by arithmetic (62 + 12 = 74), not by any signal the run produced. Recorded because it is this repo's own [`Code_Quality_Standards.md`](Code_Quality_Standards.md) §4.0 failure mode reproduced **inside the tool built to hunt for it**: a process that reports a conclusion without recording what it measured is indistinguishable from one that measured everything. **Any future catalogue pass must assert its own coverage before its findings are read.** **Cluster:** Code quality / standards coverage. **Priority:** P3. **Verdict:** build (small) for the pass; **decline** for the measure. **Severity:** no product effect and no security effect — this is a coverage question about the gates, not a defect in them. @@ -5841,3 +5845,105 @@ Both readings reach the same operational conclusion, which is the whole point of **Related:** #1078 (`new.ps1` printing cleanup advice that throws — the same script, the same "correct action, wrong accompanying claim" shape), #1060 (anchoring, which is what makes the worktree placement itself correct), #1000 (a control whose green is not evidence). **Source:** found 2026-08-07 from a `git pull --ff-only` failure while syncing a worktree, and connected to a bogus "unpushed" reading reported hours earlier in the same session — one root cause behind two symptoms that had been treated as unrelated. Mechanism confirmed at `new.ps1:133` and the `push.default` posture verified against the live config before the warning above was written. + +## 1089. HL7 `parse_path` accepts component 0, so `PID-5.0` silently reads and OVERWRITES the last component + +> 🔢 **Filed 2026-08-07 — not started.** Value **8/10** · Difficulty **2/10** · _quick win_. `parsing/peek.py::parse_path` accepts `\d+` for the component index, so `PID-5.0` parses with `comp=0`; every consumer then indexes `x[comp - 1]`, which is `x[-1]`. `msg.field('PID-5.0')` returns the **last** component and `msg.set('PID-5.0', v)` silently **overwrites** it. **The X12 twin already validates this and is tested; the HL7 side has neither the guard nor the test.** + +**Cluster:** HL7 parsing / silent data corruption. **Priority:** P2. **Verdict:** build (small). **Severity:** would put PHI in the wrong component on a first deployment, **with no exception, no `ERROR` disposition and no dead-letter** — the message delivers looking successful. Nothing is deployed (§0), so this is what a deploying site would hit, not something happening today. + +**Reproduced by execution, not inferred.** `msg.field('PID-5.0')` returns the last component; `msg.set('PID-5.0', v)` overwrites it; on an empty field the same path raises a bare `IndexError` rather than a domain error. + +**The asymmetry is the evidence it is an oversight.** `parsing/x12/message.py` raises `X12PeekError` on an index below 1 and has a parametrized test for it. The HL7 path, which is the **default** content type and the one carrying PHI, has neither. A repo-wide `grep parse_path tests/` returns zero. + +**Why nothing sees it.** mypy sees a well-typed `int`; ruff has no bounds rule; mutmut's scope is `parsing/binary.py` alone. It is reachable from operator-facing surfaces — `api/app.py`'s `field_path` query parameter and `store/content_search.py` — so the bad path can arrive from outside, not only from a Handler author's typo. + +**The fix is the guard the X12 side already has**, plus the parametrized test copied across. Test the **write** path too: a read returning the wrong component is visible to a careful operator, a write that silently replaces one is not. + +**Related:** #1093 (the inventory this came from), #1073 (the pass), #1027 (a green that is not evidence about what it appears to cover). + +**Source:** BACKLOG #1073's ASCQM catalogue pass, element `ASCSM-CWE-129` (CWE-129). Reproduced by running the two calls, not concluded from reading the regex. + +## 1090. `write_reference_snapshot`'s `json.dumps` has no `default=`, so a TOML date in a file reference source fails the sync + +> 🔢 **Filed 2026-08-07 — not started.** Value **6/10** · Difficulty **2/10** · _quick win_. `store/{store,postgres,sqlserver}.py::write_reference_snapshot` calls `json.dumps(v)` over a `Mapping[str, Any]` with **no `default=` hook**. `tomllib` materializes a TOML date as `datetime.date`, which `json.dumps` cannot encode. **The DATABASE reference source coerces its values; the FILE source does not** — one producer of the same sink was hardened and its sibling was not. + +**Cluster:** Reference data / serialization boundary. **Priority:** P3. **Verdict:** build (small). **Severity:** on a first deployment, a reference TOML carrying an ordinary `effective = 2026-01-01` would fail its sync and every Handler using that code set would then raise **with the cause obscured** — the sync logs one WARNING naming the exception class only, by deliberate design. + +**This verdict flipped from "covered" under adversarial review**, and the reason it flipped is worth keeping: the original "covered" rested on the population of serialization boundaries being closed at two. It is three. `_load_file_source` returns `dict(load_code_set(path))` uncoerced, while `_load_database_source` routes through a `_cell` coercion. + +**Why nothing sees it.** The `Mapping[str, Any]` signature defeats mypy strict at exactly the point that matters. The one semgrep rule in the neighbourhood is CWE-502 deserialization, an **adjacent** class. **Every existing test uses CSV**, where all values are already `str`, so the whole test suite is structurally incapable of reaching the defect. + +**Two candidate fixes, and they are not equivalent.** Routing the file producer through `_cell` fixes this instance; giving the sink a `default=` hook fixes the class, including the next producer nobody has written yet. Prefer the sink. + +**Related:** #1093, #1073. + +**Source:** BACKLOG #1073's ASCQM pass, element `ASCRM-RLB-3` (CWE-1070). Verified against the shipped loader and sink. + +## 1091. Hard-coded credential detection reads neither `.ps1` nor `.yaml`, and the entropy gate floors out on exactly the interesting secrets + +> 🔢 **Filed 2026-08-07 — not started.** Value **7/10** · Difficulty **4/10**. The two required gates that are supposed to stop a committed credential have a **combined blind spot that covers most of the places a credential would actually live**. bandit is a Python AST scanner and does not parse `.ps1` or `.yaml` **at all**; gitleaks' operative generic rule is entropy-gated, so a **low-entropy** credential — `password`, `changeme`, a short site code — falls below the threshold and is not reported. + +**Cluster:** Secrets / supply-chain-in. **Priority:** P2. **Verdict:** build. **Severity:** a first deployment could carry a **defaulted database credential into production with both required gates green**. Two defaulted credentials sit in the tracked tree today and pass both. + +**READ THE SEVERITY PRECISELY.** This is **not** a live exposure and must never be written as one: nothing is deployed (§0), and the two tracked values are development defaults, not live secrets. The defect is that **the control cannot see the class**, so the next one — a real credential, added by someone trusting the gate — would land the same way. + +**Where a credential would live and be unseen:** `connections.toml`, `docker/compose.yaml`, `docker/k8s/*.yaml`, `scripts/dev/*.ps1`, and `samples/config/` (which is bandit-excluded outright). + +**The `env()` guidance is not a control.** Enforcement exists at exactly three seams in `config/wiring.py`. Everywhere else "supply it via `env()`" is documentation, and documentation does not fail a build. + +**Difficulty 4 is the false-positive budget, not the rule.** A naive low-entropy rule over YAML and PowerShell will fire on every `password:` key in every sample. The item is really "extend detection to the file types the project actually uses, with an allowlist for the deliberate development defaults" — and the allowlist must be **explicit and reviewable**, because an allowlist nobody can enumerate is the gate turned off. + +**Related:** #1093, #1073, #1092 (the same shape — a control believed stronger than its measured scope). + +**Source:** BACKLOG #1073's ASCQM pass, element `ASCSM-CWE-798`. Scanner behaviour confirmed against the pinned configurations, not inferred from the tools' reputations. + +## 1092. Every gate verdict that moved under measurement moved the same way, and three of them falsify a sentence the project had written down + +> 🔢 **Filed 2026-08-07 — not started.** Value **9/10** · Difficulty **3/10**. Across a 74-element audit with an adversarial refutation stage, **eight verdicts flipped and every one flipped the same direction: "covered" to "gap." Not one "gap" was refuted into "covered."** A symmetric process would not do that. The gates in this repo are, **on measured output**, weaker than the repo's own prose about them — and this item is about the **prose**, not the gates. + +**Cluster:** Quality record / claim honesty. **Priority:** P2. **Verdict:** build. **Severity:** no product effect. The defect is that the project's own quality record asserts machine enforcement that measurement does not support, and that record is read by adopters and by future sessions deciding what is already covered. + +**Three specific written claims were falsified by measurement.** These are the actionable core: + +1. [`Code_Quality_Standards.md`](Code_Quality_Standards.md) states that import and layer rules are **"machine-checked in CI"**, and scores signal 1 **Built — Strong** on the strength of `tests/test_dependency_boundaries.py`. That test walks `ast.Import` only, roots at `messagefoundry/`, and never opens a file under `harness/`, `tee/` or `scripts/`. `harness/load/__init__.py`'s own docstring claims the package "never imports the engine's pipeline/store/config internals" — **already false, and nothing measures it.** +2. The shell-execution drift test's premise that an injectable new site **cannot land quietly**. It asserts a **file-path set**, never how a command string is built, and it exempts by name the three files that build them. Demonstrated by mutation: an unvalidated elevated `ShellExecuteW` added to `service.py` leaves both tests green. +3. The claim that the web console is the only templated-HTML surface. `ide/src/` carries roughly fifteen `webview.html` assignment sinks across twelve modules, behind five different hand-rolled escapers — one of which does not escape quotes and says so in an in-file comment. + +**The general shape, and it is the reusable part.** *A gate's name is a claim; only its measured output and scope are evidence.* Each of these three passed review for as long as it did because the reviewer read what the gate was **called**. This is [`Code_Quality_Standards.md`](Code_Quality_Standards.md) §4.0's liveness rule extended one step: §4.0 asks whether a gate **ran**; this asks whether what it ran on is **what the prose says it covers**. + +**The deliverable is a scope audit of the quality record**, not a code change: for each "machine-checked" or "enforced" claim in the scorecard, name the instrument, state its measured scope, and correct any claim the scope does not support. Signals 1, 3 and 5 are the ones this audit already reached. + +**Related:** #1093, #1073, [`Code_Quality_Standards.md`](Code_Quality_Standards.md) §4.0, #1000 (a required check green because it read the wrong directory), #1027, #1018 (guards that go quiet). + +**Source:** BACKLOG #1073's ASCQM pass. The directional asymmetry (8 of 8 flips in one direction) is the statistic that turned three separate findings into one item. + +## 1093. ASCQM gap-hunt inventory: the BACKLOG #1073 findings not filed as their own items + +> 🔢 **Filed 2026-08-07 — not started.** Value **5/10** · Difficulty **2/10**. A holding item so the remainder of the #1073 pass is not lost between the four findings that got their own numbers (#1089–#1092) and the ~19 that are correctly **null**. **Nothing here is a new investigation** — each entry names its element, its site and why no current gate reaches it. Triage and split; do not treat this as one unit of work. + +**Cluster:** Quality gates / coverage. **Priority:** P3. **Verdict:** triage, then split. **Severity:** no product effect; several entries would become one on a first deployment. + +**Closable by enabling something already installed** — highest value per effort in the whole pass: + +- **`CWE-456` missing initialization.** `mypy --strict` does **not** include `possibly-undefined`. Enabling it caught 4 of 5 probe shapes, and the corpus is **measured clean — 0 findings across 279 files** — so it adopts green with zero remediation debt. One line in `pyproject.toml`. Partial: it does not catch conditionally-bound attributes, and the item should say so. +- **`CWE-772`/`CWE-404` resource release.** Against a six-leak probe, ruff reported exactly **one** finding — `SIM115`, on builtin `open()`. A leaked socket, SMTP session, stream writer and dangling task all passed. `RUF006` is unselected; `filterwarnings` and `ResourceWarning` appear **nowhere in the tree**, so Python's own leak signal is emitted and discarded; `tests/conftest.py` autouse-mutes "Task was destroyed but it is pending" at every teardown. +- **`CWE-1121` complexity scope.** The C901 scan argument is literally `messagefoundry` in all five places, missing a complexity-92 function in the shipped web console. `test_lint_scope_parity.py` already forces whole-repo scope for the required ruff leg for exactly this reason; nothing pins this one. +- **`CWE-789` unbounded allocation.** `parsing/compression.py` decompressors default `max_output_bytes=None` and are re-exported to Handlers. + +**Needs a purpose-built check (system-level — the stated reason for the pass):** + +- **`CWE-424`/`CWE-1057` data access outside the store.** `pipeline/cluster.py` holds its own asyncpg pool and issues DDL including `CREATE TABLE nodes/leader_lease`; `cluster_sqlserver.py` drives 11 private store members. Both **deliberately avoid importing the store**, which is precisely what makes the one import-scanning gate blind. +- **`CWE-424`/`CWE-1054` client-to-engine layer skip.** `harness/load/` constructs store classes directly and reaches `_pool`. Overlaps #1092 claim 1. +- **`CWE-1088` timeouts.** `tests/test_ldap_timeouts.py` is a genuine AST gate **for ldap3 only** — and it exists because that convention already failed once. Nothing stops the identical regression at urllib, smtplib, ftplib, `socket.create_connection`, `asyncio.open_connection` or pynetdicom. +- **`CWE-89` SQL.** bandit's only SQL heuristic, **B608, is explicitly disabled**, justified by a point-in-time manual review — and a review is not a control. Every interpolated token is a code constant **today**; nothing holds it there. +- **`CWE-22` path traversal.** No shared `safe_join` choke point exists for a test to enforce, so each sink is guarded by whoever remembered. +- **`CWE-1094`/`CWE-1067` index and scan.** No `EXPLAIN` anywhere in the tree; the repo's own test plan already records this as open. + +**DO NOT file the ~19 metric findings** (file length, parameter count, inheritance depth, child-class count, literal density, outward calls). They are correctly "gap" under the element definitions and are backed by no defect. [`Code_Quality_Standards.md`](Code_Quality_Standards.md) §4.1 governs: **a count is not an item.** + +**TWO PREMISES HERE WERE NOT VERIFIED, and must be before anything rests on them.** CodeQL's actual query coverage was established from suite selectors and query tags, **not by executing CodeQL against this tree**, so roughly a dozen verdicts citing "CodeQL does not carry query X" are unconfirmed. And every "not a required context" rests on the checked-in `.github/required-contexts.txt`, not the live API — a file whose own header records the required set moving five times in one day. **A live contradiction was found and deserves its own item:** `.github/workflows/security.yml` asserts CodeQL "runs free and IS a required context" while `required-contexts.txt` lists it under DELIBERATELY NOT REQUIRED. One of them is wrong. + +**Related:** #1073 (the pass), #1089–#1092 (the findings that got their own numbers). + +**Source:** BACKLOG #1073's ASCQM 1.1 catalogue pass over all 74 live elements, with an adversarial refutation stage on every non-not-applicable verdict. From 0d69839c3558f1c529f01df3f46e0054277e8098 Mon Sep 17 00:00:00 2001 From: wshallwshall Date: Fri, 7 Aug 2026 08:34:21 -0500 Subject: [PATCH 2/2] docs(CLAUDE): the 5055 decline marker cited only the LIVE backlog file, which is where it rots Caught by the b5-backlog-archival session. The marker exists precisely to outlive #1073, and it cited `docs/BACKLOG.md #1073` -- but #1073 is closed, and closed items LEAVE that file for docs/archive/backlog/BACKLOG-CLOSED.md. The failure is invisible to every check: the markdown link to BACKLOG.md still resolves, so nothing 404s. Only the human-readable "#1073" silently stops being findable there. That is exactly how section 12's EXISTING #26/#27 pointers already went stale -- verified against origin/main: neither heading is in docs/BACKLOG.md, both are in the archive. Now names both locations rather than guessing an archive anchor that may not match the slug the archival pass generates. The repo's deep-link-to-archive convention (44 occurrences in docs/BACKLOG.md) is the precedent. NOT FIXED HERE, deliberately: the pre-existing #26/#27 staleness in the same list. It is the same defect class but not this PR's scope, and the session that found it is taking it. I had noticed the #26/#27 staleness earlier in this session and judged it "not worth chasing", then reproduced the identical defect in a marker whose whole purpose is durability. Recording that, because the lesson is not "fix the pointer" -- it is that a rot I declined to fix is one I had already stopped seeing. --- CLAUDE.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b8be4d00..2d5e90f4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -567,8 +567,11 @@ harness process only.) score collides with the anti-metric rule in [`docs/Code_Quality_Standards.md`](docs/Code_Quality_Standards.md) §4.1. **The catalogue is a different question and was adopted:** the ASCQM 1.1 weakness list is free from OMG, one bounded - pass over it ran under [`docs/BACKLOG.md`](docs/BACKLOG.md) #1073, and its findings are #1089–#1093. - Re-running that pass is legitimate; adopting the score is not. + pass over it ran under **#1073**, and its findings are **#1089–#1093**. Re-running that pass is + legitimate; adopting the score is not. *(#1073 is closed, so it lives in + [`docs/archive/backlog/BACKLOG-CLOSED.md`](docs/archive/backlog/BACKLOG-CLOSED.md) once archived, + not in [`docs/BACKLOG.md`](docs/BACKLOG.md) — a marker here has to outlive its item by + construction, so it must not cite only the live file.)* - Don't keep grinding in a polluted context — `/clear` after repeated failures. - Don't use **glyphs or emoji** in prose, comments, commit messages, PR bodies or replies — say the word (§11). The backlog status-banner alphabet is the one machine-parsed holdout; read it with