Found by the corpus wave that closed the second half of #2729. Once the rosetta gate started asserting the api rule instead of the converted orphan count, every language's api rule had to be pointed at a real public surface in that language's own vocabulary — and seven of them cannot see the idiom their own language uses to make a function public.
What the wave measured
37 of 46 corpus languages now plant a public surface their api rule matches: pub (rust, zig), public (java, csharp, kotlin, php, groovy, swift, solidity), global (apex), export (cpp, javascript, typescript, shell, makefile), a capitalised name (go), a per-function export statement (scheme, tcl, powershell, ruby), a module header (haskell), or a file-level declaration where the language has no per-function concept (dockerfile EXPOSE, yaml on:, css @property, html id=, yacc %define, m4 AC_SUBST, cobol ENTRY, agc EQUALS).
Seven cannot, and the corpus records 0 rather than manufacture a construct nobody writes:
| language |
how the language makes a function public |
what the api rule matches |
| abap |
FORM subroutines are public by default |
PUBLIC SECTION / REMOTE FUNCTION / EXPOSED / DEFINE VIEW — all need a CLASS, RFC metadata or a CDS definition |
| ada |
a subprogram declared in the package spec |
package X is — a spec, i.e. a separate .ads compilation unit |
| dart |
a top-level function whose name does not start with _ |
export / part of, annotations, and class|mixin|enum|extension|typedef declarations |
| matlab |
a function file is public by definition |
only a classdef methods block |
| objective-c |
a method declared in the @interface |
FOUNDATION_EXPORT / OBJC_EXPORT / extern / @property / IB* |
| ruby |
public :name (top-level defs are private on Object) |
module_function and Sinatra-style routes |
| shell |
export -f name |
^[ \t]*export\s+NAME — variables only, -f never matches |
Two more of the same family, found while planting: yaml's rule lists push\|pull_request\|workflow_dispatch\|issues as triggers but not workflow_call, which is the actual reusable-workflow API; perl's only rule-visible export form (@EXPORT_OK = qw(...)) also counts state_mutation, and use Exporter\|parent\|base also counts import.
Why this is one finding, not seven
The rules disagree about what "public surface" means, in both directions:
- Too narrow (the table above): the language's own visibility idiom is invisible, so the metric reads 0 for a file that exposes everything it defines.
- Too broad: java/csharp/php/kotlin count a bare
\bpublic\b, so a public field, a public class and a public method are three api hits and a modifier anywhere in the code stream counts; go's third alternative ^[ \t]+\b[A-Z]\w+\b(?!\() counts any indented capitalised token, i.e. a reference to an exported name, not a declaration.
api feeds _calc_api_exposure and _calc_documentation, so both directions land in scored risk. On the corpus this reads as language bias with nothing wrong in the corpus: identical planted intent, api 0 in one language and 3 in its neighbour, purely from what the regex accepts.
Fix shape
Give the api rule a stated contract — a declaration that makes a named function or type visible outside this file — and audit all 46 against it, rather than fixing seven regexes one at a time. The narrow half is mostly a missing alternative (export\s+-f\s+NAME for shell, public\s+: for ruby, an @interface method line for objective-c, a top-level non-underscore Type name( for dart, workflow_call for yaml). The broad half needs the \bpublic\b alternatives anchored to a declaration the way go's first alternative already is.
Per corpus AGENTS.md rule 8 and #2669's paired-work rule, any rule change here is corpus-visible: it must land with the matching plant in keyword-rosetta, or it manufactures a fresh red cell. The corpus side is ledgered meanwhile as api-no-plantable-idiom (upstream-question, citing this issue).
Part of #2669. Siblings: #2729 (the gate half, done), #2727 (orphan census is name recurrence — which is why an export statement naming its own function zeroes api_orphan_credit in assembly/powershell/ruby/tcl).
Found by the corpus wave that closed the second half of #2729. Once the rosetta gate started asserting the api rule instead of the converted orphan count, every language's api rule had to be pointed at a real public surface in that language's own vocabulary — and seven of them cannot see the idiom their own language uses to make a function public.
What the wave measured
37 of 46 corpus languages now plant a public surface their api rule matches:
pub(rust, zig),public(java, csharp, kotlin, php, groovy, swift, solidity),global(apex),export(cpp, javascript, typescript, shell, makefile), a capitalised name (go), a per-function export statement (scheme, tcl, powershell, ruby), a module header (haskell), or a file-level declaration where the language has no per-function concept (dockerfileEXPOSE, yamlon:, css@property, htmlid=, yacc%define, m4AC_SUBST, cobolENTRY, agcEQUALS).Seven cannot, and the corpus records 0 rather than manufacture a construct nobody writes:
FORMsubroutines are public by defaultPUBLIC SECTION/REMOTE FUNCTION/EXPOSED/DEFINE VIEW— all need a CLASS, RFC metadata or a CDS definitionpackage X is— a spec, i.e. a separate.adscompilation unit_export/part of, annotations, andclass|mixin|enum|extension|typedefdeclarationsmethodsblock@interfaceFOUNDATION_EXPORT/OBJC_EXPORT/extern/@property/IB*public :name(top-level defs are private on Object)module_functionand Sinatra-style routesexport -f name^[ \t]*export\s+NAME— variables only,-fnever matchesTwo more of the same family, found while planting: yaml's rule lists
push\|pull_request\|workflow_dispatch\|issuesas triggers but notworkflow_call, which is the actual reusable-workflow API; perl's only rule-visible export form (@EXPORT_OK = qw(...)) also countsstate_mutation, anduse Exporter\|parent\|basealso countsimport.Why this is one finding, not seven
The rules disagree about what "public surface" means, in both directions:
\bpublic\b, so a public field, a public class and a public method are three api hits and a modifier anywhere in the code stream counts; go's third alternative^[ \t]+\b[A-Z]\w+\b(?!\()counts any indented capitalised token, i.e. a reference to an exported name, not a declaration.apifeeds_calc_api_exposureand_calc_documentation, so both directions land in scored risk. On the corpus this reads as language bias with nothing wrong in the corpus: identical planted intent, api 0 in one language and 3 in its neighbour, purely from what the regex accepts.Fix shape
Give the api rule a stated contract — a declaration that makes a named function or type visible outside this file — and audit all 46 against it, rather than fixing seven regexes one at a time. The narrow half is mostly a missing alternative (
export\s+-f\s+NAMEfor shell,public\s+:for ruby, an@interfacemethod line for objective-c, a top-level non-underscoreType name(for dart,workflow_callfor yaml). The broad half needs the\bpublic\balternatives anchored to a declaration the way go's first alternative already is.Per corpus AGENTS.md rule 8 and #2669's paired-work rule, any rule change here is corpus-visible: it must land with the matching plant in keyword-rosetta, or it manufactures a fresh red cell. The corpus side is ledgered meanwhile as
api-no-plantable-idiom(upstream-question, citing this issue).Part of #2669. Siblings: #2729 (the gate half, done), #2727 (orphan census is name recurrence — which is why an export statement naming its own function zeroes
api_orphan_creditin assembly/powershell/ruby/tcl).