You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
arch_api conflates api-rule hits with converted orphans, and the rosetta gate reads the conflated column -- 36 of 44 languages pass api without the rule matching anything #2729
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 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 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.
Both halves are done, corpus-side, in squid-protocol/keyword-rosetta#53. No engine change was needed: raw_arch_api already exists (#2536), so the fix was to make the gate read it and then give the corpus something for the rules to find.
1. The gate.verify_language.py maps api to raw_arch_api (RAW_COLUMNS, the same column bias_report.py's RISK_INPUT_COLUMNS already used) and pins the conversion separately as api_orphan_credit = arch_api − raw_arch_api. The credit stays in the gate deliberately: it is the only assertion that the main→a→b→c chain resolved, which is how #2543 and #2540 were caught. All 46 manifests were then re-blessed mechanically from one full-precision scan — api := raw, api_orphan_credit := adjusted − raw — with no hand-edited numbers.
2. The corpus wave, the one this issue said was Batch-A-sized. 31 languages planted, one language at a time, each verified through the real pipeline:
shape
languages
api/file
per-function visibility (pub, public, export, global, a capitalised Go name, a per-function export statement)
api now fires in 37 of 46 languages, against 10 before. Result: verify_language.py 46/46 PASS, na_check.py --ci clean, bias_report.py --gateexit 0, 0 unexplained.
What the wave refused to do. dart and objective-c can be made to fire — a typedef per probe, a property per probe — and both were measured doing so cleanly. Planting a declaration nobody would write, to make a number move, is the measurement this corpus exists to avoid. perl and sqlite have no rule-visible form that does not move a planted count (@EXPORT_OK adds state_mutation; CREATE VIEW is a func_start hit). All seven are ledgered and filed as #2730: the api rule has no stated contract — seven languages' own visibility idiom is invisible to it, while java/csharp/php/kotlin count any bare public token and go counts capitalised references.
What the wave uncovered. With a real rule count and a conversion credit in the same file for the first time, data/go/a.go reads function_count 3, raw_arch_api 3, raw_state_slop_orphans 3 — and arch_api6. A function that is both declared public and uncalled is counted twice, because the Contextual Baseline Fix never asks whether the rule already counted it. That is #2731, and it is the cause of the 21 bias cells that this issue's "beyond the corpus" section predicted would matter on real code: a library's public functions are exactly the ones with no in-repo caller.
Also confirmed while planting, and ledgered as api-export-names-suppress-orphan-credit: in assembly, powershell, ruby and tcl the export statement names the function, so under #2727's name-recurrence orphan census the probes stop reading as orphans and the credit drops 3 → 0. makefile and m4 were re-planted (export VAR / AC_SUBST rather than .PHONY: probe_x / m4_provide([probe_x])) to keep theirs.
Closing when keyword-rosetta#53 merges. Open siblings: #2730, #2727, #2731.
Both halves are now verified against current main, and the close criteria this issue's own status comment set are restored.
The gate half (keyword-rosetta#53) was already done. The corpus half — the wave that gave the rules something to find — is done too, but it had drifted out of the state that comment measured: #2731 (shipped as #2734) and #2730 (shipped as #2743) both moved corpus-observed counts after it was written, so verify_language.py was failing 26 of 46 and bias_report.py --gate could not run clean. keyword-rosetta#57 re-blesses that — 100 cells across 27 languages, mechanically from one full-precision scan at engine 19262fb0, no hand-edited numbers:
api_orphan_credit 3 → 0 in 25 languages, which is arch_api counts a declared-public, uncalled function twice: 3 functions record api 6 #2731 landing: the conversion now credits only the orphans the api rule did not already declare. New ledger entry api-declared-orphans-are-not-new-surface; api-double-count-inflates-scored-api flips still_reproduces false with the go/a.go numbers that verify it (arch_api 3, was 6).
api 0 → 3/file for abap, ada, dart, matlab and perl — the five The api rule has no stated contract: 7 languages' own visibility idiom is invisible to it, and 5 others count any public token #2730 fixed at the rule rather than by inventing a plant — and c 6/9/7/7 → 4/5/3/3, the too-broad direction. api-no-plantable-idiom narrows from seven languages to objective-c and sqlite, with the reason each survivor stays (objective-c's is now a corpus constraint, not a rule gap: SPEC.md rule 2 pins class_start at 0, so an @interface cannot be planted even though the rule can finally see one).
Result: verify_language.py 46/46 PASS, na_check.py --ci clean, bias_report.py --gate exit 0 with 0 unexplained. Ledgered cells fall 194 → 184 and derived 116 → 115 — fewer cells need excusing because fewer are out of band. The headline is raw_arch_api, the column this issue was filed about: abap and ada rise 0 → 3.25, c falls 7.25 → 3.75, and the inverted finding this issue described ("those ten are the only ones measuring anything") is measured gone.
One residue found while closing this out, fixed in #2747: the recorder test was still asserting arch_api == raw_arch_api + raw_state_slop_orphans and calling it "the Contextual Baseline Fix invariant". It never proved that — both sides are literals in the fixture — and #2734 made it false in general. The only recorder-level statement of the contract was therefore the double count #2731 removed, written down as an expectation. Replaced with the recoverable-credit form (adjusted − raw, the same subtraction the corpus gate reads) plus a partial-credit test that fails against the old sum on purpose.
Closes on keyword-rosetta#57 + #2747. Remaining siblings, both still open and both independent of this: #2727 (orphan census is name recurrence) and #2728 (slicer keyword buckets reach the orphan checks).
Closing. Both halves are merged and both close criteria hold on main, verified by a fresh scan rather than inherited from this issue's earlier status comment.
The gate half — keyword-rosetta#53: verify_language.py reads raw_arch_api as api and pins the conversion separately as api_orphan_credit = arch_api − raw_arch_api. The corpus half — keyword-rosetta#53's wave, then keyword-rosetta#57, which re-blessed it against current engine main after #2731/#2734 and #2730/#2743 moved the counts underneath it.
Where the api system stands now, at engine 0e521e8d / corpus f14449e:
then
now
languages whose api rule fires
10 of 44
42 of 46
raw_arch_api in band
median 0, the ten firing read as the outliers
median 3.0, 35/46 in band
risk_api_exposure pinned at 0 by the api_hits == 0 guard
5 languages
1 (sqlite)
bias_report.py --gate
could not run clean
exit 0, 0 unexplained
The four languages still reading 0 are each accounted for: jcl and markdown define no api rule; objective-c — #2730 taught the rule to see an @interface method declaration, so the engine half is done, but SPEC.md rule 2 pins class_start at 0, so the corpus cannot plant one; sqlite — every form its rule accepts already moves a planted count.
Three things this issue's own predictions got wrong, recorded because they are more useful than the parts it got right:
Last engine residue, fixed in #2747: the recorder test still asserted arch_api == raw_arch_api + raw_state_slop_orphans as "the Contextual Baseline Fix invariant". It never proved that (both sides were fixture literals) and #2734 made it false in general, so the only recorder-level statement of the contract was the double count #2731 removed, written down as an expectation.
Siblings still open and genuinely independent: #2727 (an orphan is a name seen once in the file) and #2728 (slicer keyword buckets reach the orphan checks).
Activity
squid-protocol commentedon Sep 5, 2026
Both halves are done, corpus-side, in squid-protocol/keyword-rosetta#53. No engine change was needed:
raw_arch_apialready exists (#2536), so the fix was to make the gate read it and then give the corpus something for the rules to find.1. The gate.
verify_language.pymapsapitoraw_arch_api(RAW_COLUMNS, the same columnbias_report.py'sRISK_INPUT_COLUMNSalready used) and pins the conversion separately asapi_orphan_credit=arch_api − raw_arch_api. The credit stays in the gate deliberately: it is the only assertion that the main→a→b→c chain resolved, which is how #2543 and #2540 were caught. All 46 manifests were then re-blessed mechanically from one full-precision scan —api:= raw,api_orphan_credit:= adjusted − raw — with no hand-edited numbers.2. The corpus wave, the one this issue said was Batch-A-sized. 31 languages planted, one language at a time, each verified through the real pipeline:
pub,public,export,global, a capitalised Go name, a per-function export statement)EXPOSE,on:,@property,id=,%define,ENTRY,EQUALS)apinow fires in 37 of 46 languages, against 10 before. Result:verify_language.py46/46 PASS,na_check.py --ciclean,bias_report.py --gateexit 0, 0 unexplained.What the wave refused to do. dart and objective-c can be made to fire — a
typedefper probe, a property per probe — and both were measured doing so cleanly. Planting a declaration nobody would write, to make a number move, is the measurement this corpus exists to avoid. perl and sqlite have no rule-visible form that does not move a planted count (@EXPORT_OKaddsstate_mutation;CREATE VIEWis afunc_starthit). All seven are ledgered and filed as #2730: the api rule has no stated contract — seven languages' own visibility idiom is invisible to it, while java/csharp/php/kotlin count any barepublictoken and go counts capitalised references.What the wave uncovered. With a real rule count and a conversion credit in the same file for the first time,
data/go/a.goreadsfunction_count3,raw_arch_api3,raw_state_slop_orphans3 — andarch_api6. A function that is both declared public and uncalled is counted twice, because the Contextual Baseline Fix never asks whether the rule already counted it. That is #2731, and it is the cause of the 21 bias cells that this issue's "beyond the corpus" section predicted would matter on real code: a library's public functions are exactly the ones with no in-repo caller.Also confirmed while planting, and ledgered as
api-export-names-suppress-orphan-credit: in assembly, powershell, ruby and tcl the export statement names the function, so under #2727's name-recurrence orphan census the probes stop reading as orphans and the credit drops 3 → 0. makefile and m4 were re-planted (export VAR/AC_SUBSTrather than.PHONY: probe_x/m4_provide([probe_x])) to keep theirs.Closing when keyword-rosetta#53 merges. Open siblings: #2730, #2727, #2731.
squid-protocol commentedon Sep 5, 2026
Both halves are now verified against current
main, and the close criteria this issue's own status comment set are restored.The gate half (keyword-rosetta#53) was already done. The corpus half — the wave that gave the rules something to find — is done too, but it had drifted out of the state that comment measured: #2731 (shipped as #2734) and #2730 (shipped as #2743) both moved corpus-observed counts after it was written, so
verify_language.pywas failing 26 of 46 andbias_report.py --gatecould not run clean. keyword-rosetta#57 re-blesses that — 100 cells across 27 languages, mechanically from one full-precision scan at engine19262fb0, no hand-edited numbers:api_orphan_credit3 → 0 in 25 languages, which is arch_api counts a declared-public, uncalled function twice: 3 functions record api 6 #2731 landing: the conversion now credits only the orphans the api rule did not already declare. New ledger entryapi-declared-orphans-are-not-new-surface;api-double-count-inflates-scored-apiflipsstill_reproducesfalse with thego/a.gonumbers that verify it (arch_api3, was 6).api0 → 3/file for abap, ada, dart, matlab and perl — the five The api rule has no stated contract: 7 languages' own visibility idiom is invisible to it, and 5 others count anypublictoken #2730 fixed at the rule rather than by inventing a plant — andc6/9/7/7 → 4/5/3/3, the too-broad direction.api-no-plantable-idiomnarrows from seven languages toobjective-candsqlite, with the reason each survivor stays (objective-c's is now a corpus constraint, not a rule gap: SPEC.md rule 2 pinsclass_startat 0, so an@interfacecannot be planted even though the rule can finally see one).Result:
verify_language.py46/46 PASS,na_check.py --ciclean,bias_report.py --gateexit 0 with 0 unexplained. Ledgered cells fall 194 → 184 and derived 116 → 115 — fewer cells need excusing because fewer are out of band. The headline israw_arch_api, the column this issue was filed about: abap and ada rise 0 → 3.25,cfalls 7.25 → 3.75, and the inverted finding this issue described ("those ten are the only ones measuring anything") is measured gone.One residue found while closing this out, fixed in #2747: the recorder test was still asserting
arch_api == raw_arch_api + raw_state_slop_orphansand calling it "the Contextual Baseline Fix invariant". It never proved that — both sides are literals in the fixture — and #2734 made it false in general. The only recorder-level statement of the contract was therefore the double count #2731 removed, written down as an expectation. Replaced with the recoverable-credit form (adjusted − raw, the same subtraction the corpus gate reads) plus a partial-credit test that fails against the old sum on purpose.Closes on keyword-rosetta#57 + #2747. Remaining siblings, both still open and both independent of this: #2727 (orphan census is name recurrence) and #2728 (slicer keyword buckets reach the orphan checks).
test(core-engine): the raw/adjusted api split is not a sum (#2729) (#…
squid-protocol commentedon Sep 5, 2026
Closing. Both halves are merged and both close criteria hold on
main, verified by a fresh scan rather than inherited from this issue's earlier status comment.The gate half — keyword-rosetta#53:
verify_language.pyreadsraw_arch_apiasapiand pins the conversion separately asapi_orphan_credit=arch_api − raw_arch_api. The corpus half — keyword-rosetta#53's wave, then keyword-rosetta#57, which re-blessed it against current engine main after #2731/#2734 and #2730/#2743 moved the counts underneath it.Where the api system stands now, at engine
0e521e8d/ corpusf14449e:raw_arch_apiin bandrisk_api_exposurepinned at 0 by theapi_hits == 0guardbias_report.py --gateThe four languages still reading 0 are each accounted for:
jclandmarkdowndefine no api rule;objective-c— #2730 taught the rule to see an@interfacemethod declaration, so the engine half is done, but SPEC.md rule 2 pinsclass_startat 0, so the corpus cannot plant one;sqlite— every form its rule accepts already moves a planted count.Three things this issue's own predictions got wrong, recorded because they are more useful than the parts it got right:
go/a.gorecording 3 functions asarch_api6.publictoken #2730.publictoken #2730's rule fix, all through the api rule rather than the orphan census. Narrowed in keyword-rosetta#58 —risk-api-exposure-zero-api-shortcircuit5 languages → 1 (sqlite),risk-documentation-zero-evidence-guardretired.Last engine residue, fixed in #2747: the recorder test still asserted
arch_api == raw_arch_api + raw_state_slop_orphansas "the Contextual Baseline Fix invariant". It never proved that (both sides were fixture literals) and #2734 made it false in general, so the only recorder-level statement of the contract was the double count #2731 removed, written down as an expectation.Siblings still open and genuinely independent: #2727 (an orphan is a name seen once in the file) and #2728 (slicer keyword buckets reach the orphan checks).