fix(i18n): read refusals along a closed list of shapes, report the rest

The scan followed many shapes and called the rest unread, but each shape it learned opened a silent
branch: a parameter rebound in the body, a second visit of a followed helper, an `import app.x`, a name
bound twice or under an `if`, a spread or a store that replaces the code, a detail rebuilt from its
variable, a None reason. It now reads only what the app writes and reports everything else: a
branch the app never takes is gone (an `or`, a default, a base class's attribute, string and union
annotations, a bare module constant), a spread is clean only from an untouched `**kwargs` no call puts
a `code` in, and the detail's variable may only grow by `|=` or `["reason"]`. The real tree reads
exactly as before; every exit and every kept branch has a self-test that a mutant of it fails.

The detail test also fails a dict without a `code`, a JSONResponse sentence and a detail-less
HTTPException; the two that are no refusal (the setup gate's 503, an unmatched api path's 404) are
listed with why. The contract's assertion that compared the reason map with itself is gone.
This commit is contained in:
2026-10-31 18:45:02 +01:00
parent 5e8589bbf8
commit f8a8e905da
5 changed files with 985 additions and 373 deletions
@@ -288,9 +288,7 @@ describe("backend ids have their names in both languages", () => {
expect(reasoned.flatMap((code) => scan.codes[code]?.reasonless ?? [])).toEqual([]);
});
it("read every refusal's reasons (a scan that matches nothing is green over nothing)", () => {
const coded = [...Object.keys(REFUSAL_FAMILIES), ...Object.keys(REFUSAL_BY_REASON)];
expect(Object.keys(reasonsOf).sort()).toEqual(coded.sort());
it("every code whose reason picks the sentence is read with a reason (a scan that matches nothing is green over nothing)", () => {
expect(Object.entries(reasonsOf).filter(([, rs]) => rs.length === 0)).toEqual([]);
});