Batch D.1 of #2669. Design item — no code changed, and none should be until this is settled.
Measured against the post-Batch-C corpus (keyword-rosetta 3d94ca17, engine aacfe5da), not the
pre-A figures #2669's item text was written from.
The premise was wrong in a useful way
#2669 D.1 frames this as one family — "shape-descriptor invariance (avg_func_complexity,
control_flow_ratio, cog_raw, gini, internal density, structural_mass — 166 cells)" — with three
options that would apply to all of it: normalise against planted branch count, exclude as documented
design, or ledger as morphology.
The 160 out-of-band cells in that family today are five different mechanisms, and they want
four different answers. Only the last bucket is the design question the item was asking about.
| cells |
mechanism |
| 18 |
A — the language has zero functions, so every per-function descriptor is undefined |
| 30 |
B — control_flow_ratio, a ratio against a denominator the corpus never controls |
| 92 |
C — derived from an input metric that is itself out of band |
| 5 |
D — the slicer segmented a different number of functions than were planted |
| 15 |
E — residual: the actual open question |
A — 18 cells: markdown and html have no functions
Both report functions_found = 0, so all nine per-function descriptors are not "deviant", they are
undefined. docs/GATING.md already has the right precedent one layer over: a cell is n/a
because the rule is None. The same reasoning applies to a per-function average when there are no
functions.
Recommendation: report as n/a, not as a red cell. Corpus tooling only — no engine change, no
rebless. This is the cheapest 18 cells on the board.
B — 30 cells: control_flow_ratio divides by something the corpus never plants
total_control_flow_ratio = branch_hits / max(branch_hits + linear_hits, 1) # detector.py:1288
linear_hits is structural_boundaries. No manifest in the corpus asserts structural_boundaries
anywhere, and the bias report does not include it among its 57 metrics — so the denominator of the
single largest out-of-band metric is neither planted, gated, nor displayed.
Measured per language (struct_linear, whole-corpus scan):
| language |
branch |
structural_boundaries |
control_flow_ratio |
| solidity |
3 |
45 |
0.063 |
| ada |
4 |
40 |
0.091 |
| python |
3 |
31 |
0.088 |
| javascript |
3 |
19 |
0.136 |
| makefile |
4 |
4 |
0.500 |
| assembly |
24 |
17 |
0.585 |
The numerator is pinned at 3–4 by design across all 46 languages. The entire spread is the
denominator, which ranges 0–45.
This kills option 1 for this metric: "normalise against planted branch count" cannot help, because
branch is already controlled. The corpus simply cannot validate control_flow_ratio today.
Recommendation: plant and gate structural_boundaries in the corpus, or mark
control_flow_ratio incomparable until that happens. Prefer the former — the signal is real and
every language has the rule; it is the corpus that is silent, not the engine. Either way the bias
report should display the denominator, since a ratio cell is unexplainable without it.
C — 92 cells: composites of already-deviating inputs
cog_raw's formula (signal_processor.py ~L800):
effective_loc = min(loc, (branches + args + 1) * 10)
sum_function_impacts = ((branches + 1) * sqrt(args + 1) + 0.05 * effective_loc) * 10
Its inputs are branch, args and LOC — and args morphology and LOC normalisation are already
validated ledger entries (args-no-parameter-surface-morphology from Batch A.4, and the tech-debt
LOC artifact noted in #2653). 15 of cog_raw's 26 out-of-band languages are also out of band in one
of its own inputs; across the whole family, 92 cells are this shape.
Recommendation: not a new deviation — mark derived. This is what E.1 was specified to do (a cell
counts as "explained" when a validated ledger entry names that metric + language); E.1 just needs to
follow the derivation edge from a composite to its inputs. No engine change and no new ledger
entries — the entries already exist, the tooling does not yet connect them.
D — 5 cells: the slicer found a different number of functions
sqlite 30, dockerfile 17, livecode/lua/matlab/ruby/shell 16, makefile 14, against 13
planted everywhere else. Per-function averages move because the divisor moved.
Recommendation: ledger as morphology, per-language, naming the segmentation cause.
E — 15 cells: the real design question
Everything above is bookkeeping. This is not:
cog_raw c, dart, go, java, javascript, kotlin, php, python, rust, typescript
structural_mass c, embedded_python, python, scala, typescript
These are mainstream procedural languages whose every input is in band — branch, args and LOC
all green — yet the composite lands out of band. That points at non-linearity in the formula itself:
the (branches + 1) * sqrt(args + 1) product and the min(loc, (signals + 1) * 10) clamp amplify
differences too small to flag on their own.
Options:
- Accept and document — the composite is supposed to be non-linear (that is what makes it a
cognitive-load proxy rather than a count), so a spread on identical intent may be correct
behaviour to ledger rather than flatten.
- Re-band composites on a wider tolerance — derived metrics get a band accounting for the
amplification of their inputs, instead of the flat 25%/50% used for raw counts.
- Decompose in the report — show
cog_raw beside its three inputs, so a reader can see whether
a deviation entered through an input or through the formula.
Recommendation: 1 + 3. Document the non-linearity as intended and make the inputs visible.
Option 2 is a threshold change that would mask real regressions later.
One hypothesis I tested and rejected
It is natural to suspect the median mixes incomparable populations — 46 languages, ~19 of them
config/DSL shapes — so that "deviation from median" flags the healthy procedural languages. This is
false. Recomputing each metric's median over procedural languages only:
| metric |
median (all 46) |
median (procedural only) |
procedural cells out of band: all-median → procedural-median |
cog_raw |
0.055 |
0.050 |
14 → 14 |
avg_func_complexity |
0.188 |
0.188 |
5 → 5 |
func_internal_density |
0.034 |
0.030 |
5 → 6 |
The two populations agree on the centre almost exactly. Recorded so nobody re-proposes
population-splitting as a fix.
Adjacent decisions this pass folds in
What I am asking for
Approval per bucket, before any edit — D.2 does not start until this is settled:
- A → report
n/a for per-function descriptors when functions_found == 0 (corpus tooling)
- B → plant + gate
structural_boundaries in the corpus, and show it in the report
- C → E.1 follows derivation edges; no new entries, no engine change
- D → per-language morphology ledger entries
- E → document the non-linearity as intended + decompose composites in the report
A, C and D are corpus/tooling only. B is corpus work. E is the only one that touches
interpretation of engine behaviour, and none of the five requires an engine code change — which is
itself the main conclusion: the shape-descriptor family is largely a reporting problem, not an
engine bias.
Child of #2669 (Batch D.1), epic #2560.
Batch D.1 of #2669. Design item — no code changed, and none should be until this is settled.
Measured against the post-Batch-C corpus (keyword-rosetta
3d94ca17, engineaacfe5da), not thepre-A figures #2669's item text was written from.
The premise was wrong in a useful way
#2669 D.1 frames this as one family — "shape-descriptor invariance (avg_func_complexity,
control_flow_ratio, cog_raw, gini, internal density, structural_mass — 166 cells)" — with three
options that would apply to all of it: normalise against planted branch count, exclude as documented
design, or ledger as morphology.
The 160 out-of-band cells in that family today are five different mechanisms, and they want
four different answers. Only the last bucket is the design question the item was asking about.
control_flow_ratio, a ratio against a denominator the corpus never controlsA — 18 cells:
markdownandhtmlhave no functionsBoth report
functions_found = 0, so all nine per-function descriptors are not "deviant", they areundefined.
docs/GATING.mdalready has the right precedent one layer over: a cell isn/abecause the rule is
None. The same reasoning applies to a per-function average when there are nofunctions.
Recommendation: report as
n/a, not as a red cell. Corpus tooling only — no engine change, norebless. This is the cheapest 18 cells on the board.
B — 30 cells:
control_flow_ratiodivides by something the corpus never plantslinear_hitsisstructural_boundaries. No manifest in the corpus assertsstructural_boundariesanywhere, and the bias report does not include it among its 57 metrics — so the denominator of the
single largest out-of-band metric is neither planted, gated, nor displayed.
Measured per language (
struct_linear, whole-corpus scan):The numerator is pinned at 3–4 by design across all 46 languages. The entire spread is the
denominator, which ranges 0–45.
This kills option 1 for this metric: "normalise against planted branch count" cannot help, because
branch is already controlled. The corpus simply cannot validate
control_flow_ratiotoday.Recommendation: plant and gate
structural_boundariesin the corpus, or markcontrol_flow_ratioincomparable until that happens. Prefer the former — the signal is real andevery language has the rule; it is the corpus that is silent, not the engine. Either way the bias
report should display the denominator, since a ratio cell is unexplainable without it.
C — 92 cells: composites of already-deviating inputs
cog_raw's formula (signal_processor.py~L800):Its inputs are
branch,argsand LOC — andargsmorphology and LOC normalisation are alreadyvalidated ledger entries (
args-no-parameter-surface-morphologyfrom Batch A.4, and the tech-debtLOC artifact noted in #2653). 15 of
cog_raw's 26 out-of-band languages are also out of band in oneof its own inputs; across the whole family, 92 cells are this shape.
Recommendation: not a new deviation — mark derived. This is what E.1 was specified to do (a cell
counts as "explained" when a validated ledger entry names that metric + language); E.1 just needs to
follow the derivation edge from a composite to its inputs. No engine change and no new ledger
entries — the entries already exist, the tooling does not yet connect them.
D — 5 cells: the slicer found a different number of functions
sqlite30,dockerfile17,livecode/lua/matlab/ruby/shell16,makefile14, against 13planted everywhere else. Per-function averages move because the divisor moved.
Recommendation: ledger as morphology, per-language, naming the segmentation cause.
E — 15 cells: the real design question
Everything above is bookkeeping. This is not:
These are mainstream procedural languages whose every input is in band — branch, args and LOC
all green — yet the composite lands out of band. That points at non-linearity in the formula itself:
the
(branches + 1) * sqrt(args + 1)product and themin(loc, (signals + 1) * 10)clamp amplifydifferences too small to flag on their own.
Options:
cognitive-load proxy rather than a count), so a spread on identical intent may be correct
behaviour to ledger rather than flatten.
amplification of their inputs, instead of the flat 25%/50% used for raw counts.
cog_rawbeside its three inputs, so a reader can see whethera deviation entered through an input or through the formula.
Recommendation: 1 + 3. Document the non-linearity as intended and make the inputs visible.
Option 2 is a threshold change that would mask real regressions later.
One hypothesis I tested and rejected
It is natural to suspect the median mixes incomparable populations — 46 languages, ~19 of them
config/DSL shapes — so that "deviation from median" flags the healthy procedural languages. This is
false. Recomputing each metric's median over procedural languages only:
cog_rawavg_func_complexityfunc_internal_densityThe two populations agree on the centre almost exactly. Recorded so nobody re-proposes
population-splitting as a fix.
Adjacent decisions this pass folds in
_get_tierassignsfc/ircconstants byliteral string membership, so ~32 tier3 languages carry an automatic risk floor and ~30% less
documentation credit for byte-identical content. Should be decided on its own; the concrete
sub-item (
embedded_pythonis not the literal string"python", so it lands tier3 despite beingpython) looks like a plain bug worth fixing regardless of the wider policy question.
affirm current behaviour and record a validated
return-assignment-counted-as-mutationentry.Nothing here changes that.
went with the literal-shielding fix.
What I am asking for
Approval per bucket, before any edit — D.2 does not start until this is settled:
n/afor per-function descriptors whenfunctions_found == 0(corpus tooling)structural_boundariesin the corpus, and show it in the reportA, C and D are corpus/tooling only. B is corpus work. E is the only one that touches
interpretation of engine behaviour, and none of the five requires an engine code change — which is
itself the main conclusion: the shape-descriptor family is largely a reporting problem, not an
engine bias.
Child of #2669 (Batch D.1), epic #2560.