Repository navigation
branch has no stated contract: handler keywords, the syntax words of one conditional, and goto count as decisions in some languages and not in others #2822
Description
Part of #2812 (Phase 3; the branch row of standards/signal_contracts.py is draft). Found by the Phase 0 cause triage on 2026-09-06: keyword-rosetta's string-literal-selective-shielding ledger entry had been excusing 12 red branch cells by cross-product, and when its signal was narrowed to what a danger-only literal can actually move, five of those cells had no explanation at all. Each turned out to be documented in the language's manifest notes and never filed.
What the corpus reads
Planted: 3 decisions per language (if / else / a loop), plus 2 safety constructs. 38 languages read branch 3. These do not:
| language | reads | what the extra hit is | shape |
|---|---|---|---|
| cpp | 4 | catch in a.cpp — branch includes catch, dual with safety's try/catch |
A · handler keyword |
| ruby | 4 | rescue in a.rb — same dual |
A |
| csharp | 4 | goto — dual with high_risk_execution (fortran's GOTO shape, fortran-goto-dual-branch-highrisk) |
C · goto dual |
| swift | 4 | guard … else — the guard and its own else clause |
B · syntax words of one conditional |
| sqlite | 5 | one CASE expression — CASE/WHEN/THEN/ELSE/END all count |
B |
| ada | 4 | end if (ada-end-if-branch-inflation) |
B |
| shell | 5 | [ … ] test brackets (shell-test-brackets-in-branch) |
B |
| livecode, powershell, tcl, makefile | 4–5 | then/end if, trap/continue, trap (batch4-dual-keyword-overlaps) |
A / B |
27 of the 46 branch rules name catch, rescue, except or goto; most languages simply plant their safety construct with a token their branch rule does not happen to match. The rules disagree, and the schema comment ("Includes: if, else, switch, for, while, catch, try …") says the handler keywords should count — which 38 languages contradict.
The contract question
One sentence, in the #2730 shape: branch matches a statement that chooses between control-flow paths at runtime. The three corollaries the audit above needs:
- A handler is not a decision.
catch/rescue/except/finally/ensuremark where control arrives after a throw; the decision was made by the throw. They aresafety's (count contract corollary 4: a token owned by another rule is not a second signal). Retire them from everybranchrule and from the schema comment. - One conditional is one hit.
else,elsif,end if,then,WHEN/THEN/END, a guard's ownelse— the continuation and closing words of a construct already counted. Anchor on the opening keyword only. (ada, sqlite, swift, shell, livecode.) - An unconditional transfer is not a decision — already decided for assembly's
jmp/call/ret(assembly/agc_assembly: branch counts unconditional transfers — 23 of 25 hits are call/ret/jmp, not decisions (#2545's shape) #2764/fix(core-engine): assembly + agc_assemblybranchcounts decisions, not unconditional transfers (#2764) #2779) and forreturn(kotlin: branch rule counts 'return' — inconsistent with java/swift/c siblings #2545/fix(standards): stop counting bare return as branch in 6 languages (closes #2545) #2634);gotois the same shape (csharp, fortran, cpp's rule).
typescript has the mirror finding on safety (3 against a plant of 2): unknown ×2 and never — type keywords counted as runtime guards. It rides here because it is the same corollary-4 question from the other side, and the two rules must be audited together.
Consumers
branch feeds _calc_cog_load (cognitive load), control_flow_ratio, the cascading-flux proximity weight (spatial_correlation.py Block 6 — a handler within 150 chars of a write currently triples it in the weighted view) and the Silencer Region pairs. Corpus: risk_cognitive_load is 80% in band; the eight cells above are its low-hanging residue.
Method
The rule-contract-audit skill (worked example: #2765 / docs/state_mutation_rule_contract.md). Expect most languages to narrow by one token, the corpus cells above to land on 3 without a re-plant, and risk_cognitive_load to tighten. Corpus side: the ledger entries branch-counts-non-decision-keywords and typescript-type-keywords-count-as-safety (keyword-rosetta, Phase 0 triage PR) record the cells and cite this issue; retire them with the fix.
Activity
squid-protocol commentedon Sep 6, 2026
Another instance for this contract, measured while auditing #2806 — cobol's
branchcounts a bare out-of-linePERFORM <paragraph>, which is an unconditional call, not a conditional or a loop:PERFORMis in the alternation unconditionally, so it counts three different constructs as one:PERFORM UNTIL <cond>(a loop — a branch),PERFORM 5 TIMES(a loop — butTIMESalready counts it, a dual), andPERFORM SOME-PARA/PERFORM A THRU B(COBOL's procedure call — not a branch at all; it is the language's invocation form, which is why it is also the missing half of the #2806 census for COBOL).Measured on the language-crucible
data/cobol(570 files): 21,549branchhits, 2,191 of them (10%) a barePERFORM <para>orPERFORM A THRU Bon its own statement.Related consequence worth recording here rather than acting on: keyword-rosetta's
data/cobol/main.cblhas the same plant gap #2806 found in abap/m4/makefile/objective-c — itsDISPATCH-PARAdisplays a banner and calls nothing. Planting the canonicalPERFORM PROBE-IO. PERFORM PROBE-RISK.movesraw_state_unreferenced2.75 → 2.25 (both in band) at the cost of moving the gatedbranchcell 3 → 5 on this defect, so #2806's corpus PR deliberately left it unplanted and recorded it indocs/unreferenced_by_name_contract.md. Oncebranchstops counting a bare PERFORM, the cobol plant becomes free and should land with it.unreferenced_by_name: state the census contract, and stop asking the …
fix(standards): branch counts decisions -- not handlers, closing word…
fix(metrics): control_flow_ratio divides by coding LOC, not the bound…
fix(rules): widen globals residue forms + swift branch .default — closes