Found by the #2669 F.4 census. This is the biggest of the three and it needs a decision before anything is changed.
What the gate is actually asserting
The Contextual Baseline Fix (galaxyscope.py L2228) rewrites api in place when a file is imported:
if popularity > 0 and orphans > 0:
meta["equations"]["api"] = meta["equations"].get("api", 0) + orphans
meta["equations"]["orphaned_logic"] = 0
raw_arch_api keeps the pre-fix value (#2536); arch_api carries the sum. keyword-rosetta's verify_language.py builds its column map from RecordKeeper.SHORT_KEY_MAP, which maps api → arch_api — the conflated column. Measured per file:
| file |
manifest |
api rule finds |
orphans converted |
gate reads |
| shell/a.sh |
3 |
0 |
3 |
3 ✓ |
| go/a.go |
3 |
0 |
3 |
3 ✓ |
| python/a.py |
6 |
3 |
3 |
6 ✓ |
| c/a.c |
12 |
9 |
3 |
12 ✓ |
Shell's api rule is ^export NAME and no probe exports anything. Go's is ^func [A-Z]… and every probe function is lowercase. Both measure zero and both pass, because three uncalled functions become three API surfaces. The manifests were recorded from the adjusted column, so the conversion is baked into the expectation.
Corpus-wide, raw_arch_api is 0 in 36 of the 44 languages that define an api rule. The median is therefore 0, and the bias report flags the ten languages whose rule does fire (c, python, embedded_python, fortran, lua, scala, livecode, css, makefile, html) as the deviants. The finding is inverted: those ten are the only ones measuring anything.
Why it matters beyond the corpus
_calc_api_exposure and _calc_documentation read the adjusted api, so on real code an uncalled-but-imported function is scored as public surface. That may well be the intent (it is what #2536 argued), but it means api in the risk equations is not the api rule, and nothing in the test surface distinguishes them. It also interacts with #2727: a language whose syntax repeats function names produces no orphans, hence no conversion, hence api == 0, hence _calc_api_exposure's if api_hits == 0: return 0.0 short-circuit — which is exactly why ada, haskell, sqlite, yaml and dockerfile sit at the bottom of risk_api_exposure and risk_documentation.
Fix shape (two halves, both needed)
- Engine/test surface: make the split explicit.
RISK_INPUT_COLUMNS in keyword-rosetta's bias_report.py already maps api → raw_arch_api for exactly this reason; the gate should do the same, so api asserts what the rule found and the conversion is asserted separately (as arch_api - raw_arch_api, or not at all).
- Corpus: once the gate reads the raw column, ~36 languages go red on
api and each needs a call — either the probe grows an exported construct in that language's own vocabulary (export in shell, a capitalised func in Go, pub in rust) or the language's api rule is too narrow. That is a corpus wave of its own, comparable in size to Batch A, and should not be folded into F.4.
F.4 bakes the current behaviour into deviation_ledger.json per GATING's upstream rule and cites this issue. Part of #2669; siblings #2727, #2728.
Found by the #2669 F.4 census. This is the biggest of the three and it needs a decision before anything is changed.
What the gate is actually asserting
The Contextual Baseline Fix (
galaxyscope.pyL2228) rewrites api in place when a file is imported:raw_arch_apikeeps the pre-fix value (#2536);arch_apicarries the sum. keyword-rosetta'sverify_language.pybuilds its column map fromRecordKeeper.SHORT_KEY_MAP, which mapsapi → arch_api— the conflated column. Measured per file:Shell's api rule is
^export NAMEand no probe exports anything. Go's is^func [A-Z]…and every probe function is lowercase. Both measure zero and both pass, because three uncalled functions become three API surfaces. The manifests were recorded from the adjusted column, so the conversion is baked into the expectation.Corpus-wide,
raw_arch_apiis 0 in 36 of the 44 languages that define an api rule. The median is therefore 0, and the bias report flags the ten languages whose rule does fire (c, python, embedded_python, fortran, lua, scala, livecode, css, makefile, html) as the deviants. The finding is inverted: those ten are the only ones measuring anything.Why it matters beyond the corpus
_calc_api_exposureand_calc_documentationread the adjustedapi, so on real code an uncalled-but-imported function is scored as public surface. That may well be the intent (it is what #2536 argued), but it meansapiin the risk equations is not the api rule, and nothing in the test surface distinguishes them. It also interacts with #2727: a language whose syntax repeats function names produces no orphans, hence no conversion, henceapi == 0, hence_calc_api_exposure'sif api_hits == 0: return 0.0short-circuit — which is exactly why ada, haskell, sqlite, yaml and dockerfile sit at the bottom ofrisk_api_exposureandrisk_documentation.Fix shape (two halves, both needed)
RISK_INPUT_COLUMNSin keyword-rosetta'sbias_report.pyalready mapsapi → raw_arch_apifor exactly this reason; the gate should do the same, soapiasserts what the rule found and the conversion is asserted separately (asarch_api - raw_arch_api, or not at all).apiand each needs a call — either the probe grows an exported construct in that language's own vocabulary (exportin shell, a capitalisedfuncin Go,pubin rust) or the language's api rule is too narrow. That is a corpus wave of its own, comparable in size to Batch A, and should not be folded into F.4.F.4 bakes the current behaviour into
deviation_ledger.jsonper GATING's upstream rule and cites this issue. Part of #2669; siblings #2727, #2728.