Repository navigation
Description
Objective
Declare the remaining paying constructs — Comparison and ShortCircuit for the other twenty languages, then EagerLogical, Ternary, CaseArm, Guard, NullSafe — one role at a time across all languages, every consumer of the role in the same change. Zero drift per role.
Umbrella: R00: #1690 · Planning ID: R05: #1695
Completion prerequisites: R04: #1694 Completion of R04: #1694 includes a recorded go decision.
Why this exists
R04: #1694 proved the shape on three languages; the trails span twenty. The order is by role across languages, not by language across roles, because that is the direction the (b)-chains run — one construct found in one language and re-found in the next (#1383 → #1527 for the branch arm; #1470 → #1522 → #1530 → #1536 for word-spelled operators) — and because migrating every consumer of a role together is what stops a half-migrated role becoming the next disagreement (#1529: the Rule-9 chain walker left on the deleted terminal list in the file whose slot had just been rewritten).
Documented policy differences stay as terms in the root expressions: Bash &&/|| are not ABC conditions (#1536); Groovy's elvis counts once per token, the Kotlin reading; Ruby and/or are short-circuit; Kotlin's infix and/or/xor are eager — both operands are evaluated — and decision-counting consumers add no chain increment for them. EagerLogical adds recognition without changing current counts or Halstead classification.
Implementation steps
-
ComparisonandShortCircuitacross the remaining languages, including word spellings (Perleq/lt/cmp, Ruby===/=~, Groovy===/=~/<=>/in/instanceof) and the text-gated Tcl/iRulesBinopExproperator read. Dialect templates for js-family and tcl-family. Groovy's row, as the worked example of a node/token pairing that is comment-held today (abc/groovy.rs): ownersSpaceshipExpression,IdentityExpression,RegexFindExpression,RegexMatchExpressionprojected to their operator token; payersLTEQGT,EQEQEQ,BANGEQEQ,EQTILDE,EQEQTILDE; self-payingMembershipExpression,InstanceofExpression. -
EagerLogical: Kotlin infixand/or/xor, Perl and PHPxor. fix(metrics): Perl xor and Bash -a/-o miss metric parity #1536 treatsxoras a value, not a chain: no Rule-9 chain increment; its boolean slot pays under the ordinary slot rule (abc/php.rs,abc/perl.rs). Preserve each language's existing Halstead classification: Perl/PHP xor remains an operator; Kotlin infix names remain identifier operands. EagerLogical adds recognition, not a billing change. Bash-a/-oexclusions must name the role and preserve their existing classification elsewhere. -
Ternary(?under the ternary node only — fix(abc): generic type-parameter <> counts as conditions in Java and Groovy #1274's wildcard case; Bash arithmetic ternary; elvis asNullSafeorTernaryper the existing per-language decision, recorded on the row). -
CaseArmandGuard(C#when_clause, JavaGuard, RubyIfGuard/UnlessGuard, Rust match-armif; default/_arms excluded with the lesson-11 reason). -
NullSafe(?.,&.,??,?->): preserve per-language consumer policy. Optional navigation and null coalescing need not have identical consumers; JS-family cognitive includes??and??=. Record the distinct structural forms and consumer projections instead of imposing a cyclomatic-only rule (fix(js-family): normalize optional chaining across cyclomatic and Halstead #281, fix(cyclomatic): safe-navigation not counted for Ruby (&.) and Groovy (?./??.); stale grammar comment #452). - After each role: delete the per-language lists it replaces inside the consumer boundary; bypass gate passes. Hand-written tests that pin behaviour (not membership) stay.
Tests and edge cases
-
slot_pays_tests.rsandtests/parity/cyclomatic_cross_language_parity.rsunchanged and passing per role. - Per role, per language that has the construct: the R04: #1694 input set (slot-only, token-only, outside-slot).
- Zero drift per role; both snapshot forms; plain run. A sibling found wrong while writing its row is a separate commit with its own baseline and submodule bump, never inside the migration commit.
Acceptance criteria
- Every (language, paying-role) pair has rows or
absent(reason); nopendingcells remain for these roles. - The "must agree with those arms exactly" comment in
src/metrics/abc.rsis rewritten to say the agreement is declared in the row and checked by the census — not deleted, because the slot driver still has to be called, and that is what the hand-written behaviour tests guard.
Verification and completion evidence
- Per-role, per-language command + output table; snapshot counts;
BCA_GATE:line; behaviour-change commits listed with issue numbers.
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
Done — every paying role declared in every language
Branch
refactor/1694-1695-paying-roles(not pushed; 28 commits onbdd70eb9, R04 throughc59fcd55). Per role, one migration commit and one review-fix commit, each with a fresh-context review:87f67091…cc6829a7(8), fixf9ba41888738dd1d, fix7122d8b0c50bcf69, fix4be5dab6dd90dbf6, fixe972fa4f6c89e00d, fixacf4dfb9a6099245(Elixir Halstead scaling),cf62aa9e(feature isolation),c58ffad4(CHANGELOG)No cell of the seven paying roles is
pendingin any of the 25 languages; coverage baseline −535 ids over the step; bypass baseline 86 → 84 (Java's two guard sites); rustfmt-bail −7.dekobon commentedon Oct 24, 2026
Specification corrections, recorded on the rows:
&/|/^on twobools and Bash's[ ] -a/-oare eager, not absent (both cells wereabsent; nowowns []with the kindsexcludesd and the reason — Bash's-a/-ostay theShortCircuitrow's text-gated payer because every consumer scores them as the&&/||they spell, fix(metrics): Perl xor and Bash -a/-o miss metric parity #1536).Guardwas R01'spays neverproof row; cyclomatic pays the guard node, so the owner is the paying projection.pays nevernow has no live user (the DSL keeps the form).whenis also a_reserved_identifierat the pin (int when = 1;), so theGuardrow's owners are the clause nodes and the token isexcludesd, billing through the byte-less arm.isand Java'sinstanceofpayers are gated to their type-test owner; JS/MozJS?and PHP</>are ungated — each because error recovery strands the token under anERROR, and the arm being replaced counted it (or not). Both directions were caught by the recovery sweep or review, never by the corpus.Ternaryrow said cyclomatic does not count it; it does, through theifkeyword. Cyclomatic now reads the row at the node and declines the keyword under it.ConditionalAccessExpressionispays_itselfbeside the??payer: one node is one?, the counting consumers pay the node, and Halstead bills the bare?through the byte-less arm.scores_itselfreads only the coalescing owner — reading the access too dropped!(a?.b)'s negated-operand condition (found by the NullSafe review; fixture and row added).dekobon commentedon Oct 24, 2026
Gates. Both snapshot forms (forced-pass and plain) exit 0 with no stranded snapshot files; the output submodule is untouched. The pre-commit gate reports pass on every commit; chain-audit exits 0; the scaling benchmark passes, including the two new probes
cyclomatic/tcl-wide-switch(0.93) andhalstead/elixir-repeated-guards(0.98); the per-language membership comparison against origin/main loses no build; single-feature library builds for rust, go, elixir, tcl and kotlin are green (the last caughtcase_armsgated per language while the tables importing it are not, fixed incf62aa9e).dekobon commentedon Oct 24, 2026
Zero drift. Every ops and metrics output was byte-compared against the R04 head binary on every population after the final commit: Bash 144 files, Cpp 1048 (both dialects), C# 22, Java 12, JavaScript 389 (both dialects), Python 73, Rust 173, TypeScript 8, TSX 1, plus a fixture per language spelling every row kind: 0 of 6,638 outputs differ.
dekobon commentedon Oct 24, 2026
Recovery sweep. Every fixture truncated at each line boundary, at sixteen offsets, and one byte after every operator punctuation (R04 and R05 fixtures, both binaries): 4,388 outputs, 0 differing.
dekobon commentedon Oct 24, 2026
Drifts found in flight, each fixed before its commit landed (none accepted as a snapshot):
&&reaching Rust's ABC chain walker (two serde corpus files)is, JS/MozJS?, PHP's orderings and iRules' switch arm stranded under anERRORdekobon commentedon Oct 24, 2026
for/switchlosing their command readingTwo complexity regressions found by review, both fixed with a scaling probe: Tcl's per-arm rescan of the switch's options, now memoised per word list; and Halstead asking Elixir's guard-position climb once per
when, now a hand-written reader as Rust's and Bash's are.dekobon commentedon Oct 24, 2026
Gate predicates the rows read moved into the parse crate: the Elixir decision module (a move) and the new
case_armsmodule. Perturbations (one payer or gate removed per language per role): every one failed at least one test; counts are in each commit message. The final branch review recorded no further findings beyond the Elixir scaling fix. Not pushed; no PR opened.dekobon commentedon Oct 24, 2026
Representation added to the row macro: a conditional self-paying owner, a text-gated payer, a first-declared-payer-child fallback for the projection where the grammar names no field, and empty owner and payer constants on an absent row. The Python reader reads each of them.
dekobon commentedon Oct 24, 2026
All acceptance items ticked; evidence in the comments above. Closing as complete. The branch
refactor/1694-1695-paying-roles(cf62aa9e) is not pushed and no PR is opened — that is the next step, outside this issue's scope.dekobon commentedon Oct 24, 2026
Superseded names (cleanup after close). Four cleanup commits landed on the branch after this issue closed, so several names cited above no longer exist at HEAD:
c_family_gates!,cpp_family_gates!'s operator fns,js_family_operator_gates!,js_family_null_safe_gate!,tcl_family_gates!,c_family_case_arm_gate!,js_family_case_arm_gate!,cpp_family_operator_in, and every per-languagecomparison_operator/short_circuit_operator/null_coalescing_operator/elvis_operator/defined_or_operator/under_*/is_case_label/safe_navigationgate fn — replaced by the DSL's ownerholds_payerand payerunder P/under [P, Q](06a68274,894059a6).cpp_operator_is_appliediscpp_family_operator_is_applied.js_abc_compute!/ts_abc_compute!→ onejs_family_abc_compute!; Mozcpp's ABC/cognitive impls →cpp_family_abc!/cpp_family_cognitive!; C and ObjC cyclomatic →impl_cyclomatic_c_family_rows!withextra = [..].pays_at_token_kind;scores_itselfleads withrows_own!;owns_via(payer, …)replacesowns && project == K.dekobon commentedon Oct 24, 2026
Post-close review found two zero-drift breaks, both on recovery shapes the branch's sweep could not reach (it cut after operator punctuation, not after keywords): Groovy's stranded
instanceof({ o instanceof }at end of input) paid an ABC condition, and Elixir'swhennamed in&when/2billed as a Halstead operator. Fixed inaf4fc7cb(Instanceof under InstanceofExpression; the Elixir reader declines anoperator_identifierparent), each with a regression row verified by perturbation. The recovery sweep now cuts after every word as well: 14,588 outputs, 0 differing; corpus oracle 0 of 6,638. Lesson 102 indocs/development/lessons_learned.mdrecords why the sweep missed them.Also in that batch:
project()reads its field by grammar id (cached per row), Perl ABC'sscores_itselfscans once, and two pre-existing quadratics the rows became the reader of — Elixir repeated guards under cyclomatic/ABC, Pythonandchains under cognitive — are linear, with probes.test(roles): pin the Bash and Elixir owner gates no consumer reads
test(roles): hold each Bash payer to its own row, on valid Bash
dekobon commentedon Oct 25, 2026
R05 verified on the current base (
7425561f) before R06/R08 start.Traceability correction. The comments above say the branch
refactor/1694-1695-paying-roleswas "not pushed, no PR". It has since been rebased onto the955da8d5..44184ad7getter-fix batch and fast-forwarded intomain; the branch ref no longer exists and the hashes cited above (c59fcd55,cf62aa9e,af4fc7cb, …) are dangling. The same commits onmainaref23fb7bc..c65df636(R04 + R05, 28 commits),ac3f7781(the rebased twin of the post-closeaf4fc7cb; same message, only rebase context differs), and6c9818cf..7425561f(five later post-close commits: review findings, the Kotlin-only gate, lesson 102, the Perl/Elixir guard-chain memo, the Bash/Elixir owner-gate tests).main == origin/main == 7425561f.Endpoint behavioural verification (
44184ad7, the revision immediately before the rebased R04/R05 sequence, vs7425561f; binaries built from each in separate worktrees). This establishes that the inherited result preserves behaviour on today's base; the per-role zero-drift evidence for each historical commit is in the comments above and is not re-run here./tmp/opencode/1696/oracle.py): per file, CLIops,metrics,dump,find -t call|string|function,count, plus a library-level verbatim record (Ast::parse(Source::new(..))on the raw bytes, no newline normalisation:metrics,ops, the Alterator-built AST export with span/comment on and off,findas(kind, kind_id, start, end),count). 25 populations —bash 146, c 3, cpp 1050, mozcpp 1050, ccomment 6, preproc 6, csharp 24, elixir 3, go 3, groovy 3, irules 3, java 14, javascript 392, mozjs 392, kotlin 3, lua 3, objc 3, perl 3, php 3, python 75, ruby 3, rust 175, tcl 3, typescript 10, tsx 2— with 42 new fixtures spelling every opener / closure / container / member / call / string kind per language (childless and keyword-truncated forms included). The harness fails on any non-zero exit. 0 of 26,992 outputs differ; 0 failed executions./tmp/opencode/1696/recovery_oracle.py, lesson 102): every fixture cut at each line boundary, 16 offsets, after every operator/punctuation character, and at the start, end and end+1 of every word; CLI (ops,metrics,find call,find string,dump) and the verbatim oracle through both binaries. 129,042 outputs, 0 differing, 0 failed executions.role-censusOK (25 × 29; 390 pending, 335 declared or absent; nopendingcell for any of the seven paying roles),role-bypassOK (84 baselined),role-coverage --require-observationsOK (6,863 baselined);make pre-commit→BCA_GATE: pass (gate=pre-commit).--no-fail-fast, exit status cross-checked): dropping Groovy'sInstanceof under InstanceofExpressionfailsgroovy_rows_feed_each_abc_path_independently; dropping the Elixir guard reader'soperator_identifierdecline failselixir_comparison_and_short_circuit_spellings_bill_1695. One behavioural test each, as the fix commit claimed.bench-scaling,chain-audit, the per-language membership comparison and the forced-pass snapshot inventory — each reported above on the pre-rebase branch; R06 runs all four again as part of its own evidence.Reviewed (astra-review, fresh context): the first version of both oracles compared failed executions as equal — fixed to fail closed and re-run; the counts above are from the fail-closed runs. No production residue found. R06 (#1696) and R08 (#1698) proceed from
7425561fonrefactor/1696-1698-recognition-roles.