Repository navigation
Description
Objective
Declare Comparison and ShortCircuit — owners, payers, projection — in Java, Python and C++, and route every consumer that reads either (ABC's token arm and scores_itself, cyclomatic's short-circuit arm, cognitive's boolean-sequence arm, the getter's operator arm) through the rows in one change. Zero drift. This is the architectural go/no-go: R05: #1695–R11: #1701 do not start until it records "go".
Umbrella: R00: #1690 · Planning ID: R04: #1694
Completion prerequisites: R03: #1693
Status: implemented on branch refactor/1694-1695-paying-roles at c59fcd55 (eight commits on bdd70eb9; not pushed). Decision: go — recorded on #1690. Zero drift measured on the corpora, the fixtures and a recovery-tree sweep; see the completion comment for the evidence tables and the perturbation table.
Adjustments to this body, recorded during implementation (each a specification correction, not a behaviour change):
- Java's
instanceofpayer is the keyword, gated to itsinstanceof_expression, not the self-paying expression node: Halstead bills the keyword, ABC billed the node, and one payer must serve both. The gate matters — a keyword error recovery strands under anERRORwould otherwise score a condition the node never did. - Halstead has a named complement.
Getter::get_paying_role(new, emitted bypaying_roles!) reads the rows betweenget_keyword_roleand the byte-lessget_op_type. Each language's byte-less arm keeps the spellings the row's application gate leaves out (Java generic</>and recovered tokens; Python loopin; C++ overload names, reference declarators, requires conjunctions) under arole-bypass: allownaming them. For those kinds the row is not what makes Halstead bill them. This is the per-language escape the acceptance criteria asked to be named. cpp_operator_is_appliedlives in the AST crate (roles::c_family::cpp_family_operator_is_applied) so the rows can gate on it; theapplied_ifmacro escape is gone.- Representation:
role_table!output ispub; fragments splice intopayslists with inline gates;project()falls back to the owner's first declared-payer child when the grammar names no field. The C-family ABC slot helpers take aScoresItselfparameter so C / Objective-C keep the name-keyed predicate until R05. - Contract 5's TS/TSX fold landed here too (
ts_family_modifiers!/ts_family_decl_keywords!).
Why this exists, and why it is the go/no-go
The deepest (b)-class chain in the tracker is the ABC slot/token pair: five languages, five issues, every sweep clearing the next victim (#1383 → #1421 → #1453 → #1523 → #1527). The machinery that landed to end it — #1526's count_boolean_slot — produced four more within two days (#1529, #1533, #1527, #1537), because it centralised the rule and left scores_itself and wrapper_operand as per-language lists that must agree with a third list by comment (src/metrics/abc.rs, "must agree with those arms exactly").
The exploratory inventory identified owner/token pairings but did not establish either new live drift or exhaustive agreement; the required agreement is between ABC's slot predicate and its own counting paths. That is what a construct row is intended to declare once. The design review's blocking finding was that a flat kind role cannot serve both — scores_itself receives BinaryExpression, the token arm fires on LT — so this package is where owners/payers/projection is proven or found wanting.
Two roles rather than one, because Comparison alone touches no cyclomatic arm (comparisons are not cyclomatic decisions) and would leave the three-consumer case unproven. ShortCircuit is read by all of ABC, cyclomatic and cognitive. The three languages cover the hard cases: Java needs the projection and keeps its AssignmentExpression recursion as a named per-language extra; Python has an owner-only, never-paying BooleanOperator for ABC that cyclomatic does count via the And/Or tokens (cyclomatic/python.rs) — the same row, two policy expressions; C++ has ConstraintReach (requires suppresses every decision in its subtree — #1525) and spells && in fold expressions, where cpp_operator_is_applied counts the fold once through its operator field.
Implementation steps
-
roles/java.rs,roles/python.rs,roles/cpp.rs(via R03: #1693's template, with Cpp extras): fullComparisonandShortCircuitrows. Java:LT/GTgated underbinary_expression(fix(abc): generic type-parameter <> counts as conditions in Java and Groovy #1274),InstanceofExpressionself-paying (decide: four uncounted boolean operands, and whether terminal sets should be token arms #1461). Python:ComparisonOperatorself-paying;BooleanOperatorowner,And/Orpayers. C++: comparison and&&/||tokens gated by the existingcpp_operator_is_applied(binary and fold parents; reuse, do not re-derive). - Root policy for the three languages:
abc::scores_itself = owns_Comparison(projected) || owns_ShortCircuit || owns_Ternary || <lang>_extra(expr)where the extra is the existing assignment-recursion fn; the ABC token arm readspays_at(Leaf)for Java and C++ andpays_at(Wrapper)for Python; Python's ABC expression omitspays_ShortCircuit(owner-only, fix(abc): if predicate scores 0 for -x, this, *p across langs #1526) while its cyclomatic expression includes it; the root's cyclomatic expression readspays_at(Leaf); cognitive'scompute_booleans_keyedkeeps its keyed projection — C++ mapsandand&&to one sequence key (cognitive/cpp.rs), so the row supplies the kinds and the per-language key fn stays beside the table; the root's Halstead expression readspays_at(Leaf)and its ABC expression for Python readspays_at(Wrapper)— theComparisonOperatorwrapper — which is the per-consumer split the fixture exists for. Every other role's arms are untouched. -
ConstraintReachunchanged; a comment at the call site states the row is consulted under it. - Delete the three languages' comparison and short-circuit lists from
abc/*.rs,cyclomatic/*.rs,cognitive/*.rsand the getter; bypass gate passes with no new exemptions.
Tests and edge cases
-
src/metrics/abc/slot_pays_tests.rsrows for the three languages unchanged and passing — they carry cyclomatic as an oracle ABC did not write. - Lesson 50's independent-path inputs per language: a bare-identifier predicate (only the slot pays), a comparison predicate (only the token pays), a comparison outside any slot (
boolean b = x < y;— only the token pays, no slot to compensate), a C++ fold(a && ... && b), arequires (A<T> && B<T>)whose&&must score zero, and a mixed-spelling C++ chaina and b && cwhose cognitive boolean-sequence score is unchanged. - Perturbations, each with the observed failing tests recorded (do not require exclusivity — the slot can compensate for a dropped token inside a predicate, which is why the outside-slot fixture exists): (1) drop
LTfrom Java's payers; (2) projection returnsNone; (3)BinaryExpressionlisted as a payer — census rejects at expansion. - Zero drift on the corpus for Java, Python, C++: metric JSON and space trees byte-identical; both snapshot forms counted; plain run; no
.snap.newundertests/repositories/big-code-analysis-output/. -
make bench-scalingon the comparison-chain and&&-chain probes;make chain-audit.
Acceptance criteria
- Every consumer of the two roles in the three languages reads the row; no comparison or short-circuit kind matched inside the consumer boundary for them.
- Zero drift evidenced per language with commands and output.
- A go/no-go note on the umbrella: did owners/payers/projection hold for the projection case, the owner-only-for-one-consumer case, the fold case and the suppression case? Name any per-language escape needed beyond the named extra. R05: #1695+ proceed only on a recorded "go".
Verification and completion evidence
- Perturbation table (three rows, observed failures each); corpus diff commands and snapshot counts;
bench-scalingexponents;chain-auditpass;BCA_GATE:line.
Activity
dekobon commentedon Oct 23, 2026
Tracking is ready
The body now links the master issue and the work packages it references. Native parent and blocked-by relationships match the listed completion prerequisites. Implementation and acceptance checkboxes remain open; prerequisite closure and the R04: #1694 go decision govern execution order.
dekobon commentedon Oct 24, 2026
R04 is implemented on
refactor/1694-1695-paying-roles(not pushed), eight commits onbdd70eb9,eca909a0throughc59fcd55: the representation (eca909a0), Java (283ce8cd), Python (ad636c57), the C++ dialects (4c129579), the TS/TSX keyword fold contract 5 assigned here (fd300ade), the changelog, and two review follow-ups (cdf5df46,c59fcd55).Specification corrections, recorded before the rows were written
instanceofpayer is the keyword, not the expression. The body saysInstanceofExpressionself-paying (R01's proof row). Halstead bills theinstanceofkeyword and ABC billed the node; one payer has to serve both, and the keyword is it —Instanceof gated_by under_instanceof_expression, the expression an ordinary owner. One pays per test, plain or Java 16 pattern form; the count is unchanged. The gate is not decoration: error recovery strands a keyword whose expression it lost (return x instanceofat end of input) under anERROR, and the ungated payer scored a condition there that the expression arm never did (review finding, fixed inc59fcd55).Getter::get_paying_role(emitted bypaying_roles!(lang)from the two rows) is read betweenget_keyword_roleand the byte-lessget_op_typeon every path the walk takes. A row gates its payers on application — the gate the counting consumers need — but Halstead bills the non-applying spellings too: Java's generic</>and a recovered token, Python's loopin, C++'soperator<,int&& x,void f() &&, a requires-clause conjunction. Those stay in each byte-less arm under arole-bypass: allow(Comparison, ShortCircuit)exemption naming them. For those kinds the row is not what makes Halstead bill them — the vocabulary is the union of row and arm. This is the per-language escape the acceptance criterion asked to be named; the pilot proves shared recognition for the three decision consumers plus a vocabulary-preserving Halstead union, not the elimination of every independent membership list.cpp_operator_is_appliedmoved to the AST crate asroles::c_family::cpp_family_operator_is_applied, since the rows gate on it; the root'sapplied_ifescape inimpl_cyclomatic_c_family!is gone and the C++ dialects useimpl_cyclomatic_c_family_rows!.role_table!output ispub(the root crate readsroles::<lang>::<role>);..frag!()splices intopays [..]lists with inline gates (LT gated_by applied,), the gate resolving at the splicing row, read byutils/role_tables.pytoo;project()falls back to the owner's first declared-payer child when the grammar names no field (Java'sinstanceof_expression).Node::count_specific_ancestorstakesimpl Fnforcheck.ScoresItselfparameter: Cpp/Mozcpp pass row-keyed closures, C/Objc keep the name-keyed predicate until R05.The four hard cases
BinaryExpression→operatorfield;InstanceofExpression→ theinstanceofchild by the payer fallback;projection_lands_on_each_owners_paying_childpins both formscomparison::pays_at_wrapperandshort_circuit::pays_at_leafonly to find the chain whose operand slots pay; cyclomatic reads the same leaf and counts it;python_rows_feed_each_abc_path_independentlyhas both policies side by side with cyclomatic as oracleFoldExpressionis an owner of both rows, gated on itsoperatorfield; the payer'sappliedgate admits only the field token, so a binary fold pays once;(ts && ...), `(...ConstraintReachuntouched; the rows are read under it with the call-site comment saying a parenthesised constraint is an ordinarybinary_expressiononly the clause above can disqualify;requires A<T> && B<T>,requires (A<T> && B<T>)andstatic_assert(requires { a && b; })score zero in the testsWhere the representation bent: the
projectfallback embeds "first declared-payer child" in the macro rather than a per-owner projector — adequate for every owner here, and a named per-row projector is the right move before a text-gated or multi-token construct needs one (R05 note).scores_itselfasks the rows about a peeled descendant withAncestors::unknown(); sound because none of the twelve owner gates reads ancestors (each reads the node'soperatorfield or is unconditional), documented at each call site, and not a licence for an ancestor-gated owner row later.Zero drift
/tmp/opencode/1694/oracle.pyrunsbca ops --no-config --language L -O jsonandbca metricslikewise over every corpus file plus one fixture per language spelling every row kind, before (bdd70eb9) and after each commit,diff -rqof the trees:--language mozcpp)Recovery sweep (
recovery_oracle.py): each fixture truncated at every line boundary and sixteen byte offsets, both binaries, ops + metrics, every dialect: 780 outputs, 0 differing.INSTA_FORCE_PASS=1 cargo test --workspace --all-features --no-fail-fast→ 6346 passed, 0.snap.new, 0.pending-snap, output submodule clean; plain run identical.Gates
Bypass baseline 125 → 86 (every Java and Python comparison / short-circuit site; the C++ sites were name-keyed and invisible to it), remove-only. Census 501 → 495 pending. Coverage −36 (
&&,||,instanceofin Java;and,or,boolean_operatorin Python; 15 ids per C++ dialect). Six newrole-bypass: allowexemptions, all the Halstead complement arms or the C++ cognitive sequence key, none for membership. rustfmt-bail +8, all in the two cyclomatic*_rows!macro bodies (cause 2, metavariable patterns) and the Java getter guard arm.make pre-commitonc59fcd55→BCA_GATE: pass (gate=pre-commit).make chain-audit4261 passed.make bench-scaling: all 82 probes within bound;cyclomatic/cpp-and-chain1.01,cognitive/cpp-and-chain1.02,abc/cpp-and-chain0.99,halstead/nested-not1.00. Feature isolation with-p big-code-analysisunder--features java|python|cpp|mozcpp|go(the last compiles none of the new tests).make check-test-lang-gates-compare COMPARE_REF=origin/mainOK.Perturbations (observed failures,
--no-fail-fast, lib targets + parity)LTfrom the payersjava_assignments_with_conditions,java_generic_angles_are_one_pair_1559,comparison_row_separates_the_owner_from_its_tokenprojectreturnsNoneBinaryExpressionlisted as a payerLTEQjava_comparison_and_short_circuit_spellings_bill_1694(Halstead)InstanceofInstanceofungatedInbare_in_and_is_pay_only_under_the_wrapperLTEQOrLTEQGTAndappliedtrue for every parentFoldExpressiondropped fromShortCircuitownersReviews: astra-review after the Java slice (four findings, fixed in
cdf5df46: a bypass-gate hole,projectunable to reachinstanceof, a recovery-tree Halstead drift for<, a missing Python self-test) and after completion (three findings, fixed inc59fcd55: theinstanceofrecovery drift above, a cached-kind hole in the forwarding-macro exemption, two overstated test comments). The second review's verdict was "not go for that HEAD" on the two drift / gate findings and "the four hard cases otherwise hold"; both are fixed and re-measured.dekobon commentedon Oct 24, 2026
Completed on
refactor/1694-1695-paying-roles(c59fcd55), reviewed twice with go recorded on #1690; the follow-on R05 (#1695) is done on the same branch, which carries both. Closing as complete; the branch is not pushed and lands with #1695.