Repository navigation
Description
Objective
Document the role tables for contributors, rewrite the rules that now describe the old shape, record the lesson, and close the umbrella with evidence of what is verified, what stays manual, and what changed.
Umbrella: R00: #1690 · Planning ID: R11: #1701
Completion prerequisites: R01: #1691, R02: #1692, R03: #1693, R04: #1694, R05: #1695, R06: #1696, R07: #1697, R08: #1698, R09: #1699, R10: #1700
Why this exists
The rules under .claude/rules/ and the lessons file are what the next contributor — and the next agent batch — read before touching a dispatch table. After R01: #1691–R10: #1700 several of them describe code that no longer exists (grammar-dispatch.md §4's is_else_if table, §1's "centralised alias sets live in kind_sets.rs"), and one of them (§7's cross-walk) must stay as manual guidance for what the gates cannot see. Leaving them stale reintroduces the problem this series exists to end: a reader following the old rule writes a new per-language list.
Implementation steps
-
roles/mod.rsmodule doc: the row grammar, every role with one sentence, owners/payers/projection,absent/Excluded/pending/text_gated_by, the three gates and the four things they cannot catch. -
.claude/rules/grammar-dispatch.md: §1 (aliases now in rows), §4 (row forms), §5 (declared owners ∩ declared payers = ∅ is the census; per-consumer projections make a deliberate wrapper+leaf count visible); §7's cross-walk kept and reworded as the manual check for wrong-role claims and incomplete entries..claude/rules/macro-comments.md: the row-comment convention.AGENTS.md:roles/in "Project layout", the three gates in "Validation gates". -
docs/development/lessons_learned.md: record the supported conclusion. Rule-sharing left membership gaps at callers (metrics(halstead): generalize the per-language string-interpolation operand skip (9 duplicated sites, recurring regression class) #420 → fix(halstead): string-interpolation operand miscounts in Groovy (GString) and Kotlin ($name short form) #454; fix(abc): if predicate scores 0 for -x, this, *p across langs #1526 and its follow-ups). Sharing membership reduced design(halstead): bill <T as Tr>, delegate*<> and super<A> as <>? #1567's repair to three table entries; it did not prevent that omission. Identify the overlapping review-entry classification as such, and document the checks this mechanism still cannot replace. - Book:
metrics.md's lambda paragraph and the ABC conditions section reference the rows;README.ja.mdin step with anyREADME.mdedit. - Each package already landed its own CHANGELOG entry; this one adds the closing note.
- Name the
pays []row mode (from R07's close-out, comment §1):canonical()emits the mode besidepays_never,utils/role_tables.pyemits the same, sopython_reader_agrees_with_the_compiled_tablescan tell a split-empty row from an owner-paying one; migrate the 23 rows if the spelling changes, baselines byte-identical. - Decide the
Switchrow's body projection (§2): a DSLbodyattribute claimingswitch_blockfor Java and Groovy, or a documented convention for a named structural fn beside the table; the module doc states which. - Tier-1 homes for the series' drift drivers (§3) — done by chore(utils): tier-1 homes for the role-series drift drivers and the R07 fixtures #1744 (branch
fix/issue-1744, pending merge):utils/drift-oracle.py,utils/drift-compare.py,utils/recovery-oracle.py,utils/perturb.py,utils/coverage-diff.py, each with a*-test.pyinmake pre-commit;verbatim-oracleis a[[bin]]ofbig-code-analysis-bench.ir_check.sh(the §4.3 IR miss-path probe) andsnapdiff.pyare one-off and are not landed; the refactor(cognitive,nexits): declare Branch, Switch, Loop, Catch, ElseIf and Exit #1697 records zip holds them if a future package needs the shape.
Acceptance criteria
-
rumdlclean;make pre-commitBCA_GATE: pass. - Umbrella closed with a completion comment giving: the census matrix with zero
pendingcells; the final coverage-residue size per language; the bypass baseline at zero; the number ofkind_id()dispatch sites removed inside the consumer boundary (before/afterrg -c); the issues closed by the series; and an explicit list of what remains manual (wrong-role claims, incomplete entries, text-keyed policy, dead walker paths).
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 31, 2026
Steps added from R07's final reviews (#1697)
The two closing reviews of R07: #1697 — the epic-alignment audit and the architecture review — surfaced three items that are DSL-grammar or series-closing work rather than R07 defects. They are R11's by its own scope ("
roles/mod.rsmodule doc: the row grammar"; the umbrella's completion evidence rests onrole_tables.pyreading every row faithfully), so they go on this package rather than into standalone issues.1. Name the
pays []row mode and make the Python reader see itpays []— a split row with no payers — is a fourth row mode spelled as an empty list. Inrole_table!it selects[split](roles/mod.rs:713-719), soOWNERS_PAYisfalseand only apays_itselfowner pays at the leaf. Butcanonical()emitsowners=[…] payers=[] wraps=[…] pays_never=false, which is byte-identical to an owner-paying row with the same owners (roles/mod.rs:830-848), andutils/role_tables.pyrecordspays_mode = "split"only from the presence of apays [..]list. Sopython_reader_agrees_with_the_compiled_tables(roles/mod.rs:1554) cannot tell the two apart, and a reader that misread the mode would still pass. Today 23 rows use the shape — 22ElseIfrows plus PHP's. The split-empty semantics are pinned bya_split_row_with_no_payers_pays_only_at_its_self_paying_owners(roles/mod.rs:1990) on the compiled side only.pays only_marked, or keeppays []and emit the mode) and havecanonical()emit it as a field besidepays_never(e.g.mode=owners|split|never), so the differential test discriminates.utils/role_tables.pyto emit the same field frompays_mode; extendutils/role_tables-test.py(or the gate self-tests) with a row of each mode.pays []besidepays never(R07,7d8c1eea); update it to whatever spelling lands.2. Decide the
Switchrow's body projectionis_switch_body_kind(roles/java.rs:53-62,roles/groovy.rs) names theswitch_blocktheSwitchowner nests its arms in: a structural fact about the construct, pinned byswitch_body_is_the_owners_child_and_no_rowsincontrol_flow_tests.rs, but a free fn beside the table rather than a row attribute, andswitch_blockstays an unclaimed kind in.role-coverage-baseline.txt. Only cognitive reads it (it opens its nesting level at the body so a ternary or lambda in the switch's subject does not inherit a level). Two grammars want it today.bodyattribute on theSwitchrow (Switch { owns [SwitchExpression(body SwitchBlock)] pays [Switch] }) that claims the kind for coverage and gives consumers abody_kind()projection, or record in the module doc that a consumer's structural projection of an owner's child lives as a named fn beside the table with a row test pinning it. Either is acceptable; the doc step must say which and why.is_switch_body_kindfns and their// role-bypass: allowsites go, andswitch_blockleaves the coverage baseline.3. Tier-1 homes for the series' drivers
R07 (and R06 before it) measured zero drift with a set of drivers that live only in the working caches (
.claude/rules/temp-files.mdtier 2), which is a citation to nothing once the host is reimaged. The evidence-record comment on #1697 carries the commands and revisions, so the claims are reproducible; the drivers are not in the tree. They will be needed again for R09 and R10, which is why this is a closing step here rather than an R07 follow-up:oracle.py— runsbcaand the verbatim-oracle over the integration corpora + fixture roots per language and writes one JSON per (file, metric view).compare.py— diffs two oracle output trees and prints the differing/compared/key-problem summary.recovery_oracle.py— the error-recovery-shape sweep (stranded C# catch, Perlwhen, Bash heredoc-in-compound).perturb.py— the production-line perturbation sweep (reads each file once, writes mutants from the in-memory copy, restores infinally; see.claude/rules/testing.md).coverage_diff.py— covered-count (not percentage) diff per file between twollvm-covJSON reports.ir_check.sh— the iRules/Tcl parity probe.Move each into
utils/with a*-test.pyself-test wired intomake pre-commit, following the existing gate conventions (repo root fromPath(__file__),tomllib, no cwd dependence), or decide per driver that it is one-off and say so on this issue. The verbatim oracle's Rust source (verbatim-oracle-src/) needs the same decision — it may belong underbig-code-analysis-bench/orxtask/.The 21 R07 control-flow fixtures and 5 C5b fixtures become R10's corpus rows (noted on test(parity): derive cross-consumer parity from the role tables beside the hand-written suites #1700); nothing to do here beyond confirming they landed.
Added by the R07 close-out; #1697's completion comment lists these as its deferred items.
dekobon commentedon Nov 1, 2026
R07's three close-out items on this issue are now tracked as their own sub-issues so each lands with its own closing keyword and this issue stays open for the actual close-out:
ir_check.shandsnapdiff.pyare one-off and not landed, and corrects the fixture inventory (25 distinct files, none a recovery shape).pays []mode → refactor(roles): emit the pays mode in canonical() and role_tables.py #1745. Count corrected: 21 rows, allElseIf(not 23 / "22 + PHP").refactor/1699-doc-rolesdeclaresHoldsDocInBody { owns [StaticInitializer, Block, SwitchBlock] }for Java, which makes the current "no row claims it" wording (roles/java.rs:53-59,control_flow_tests.rs:427-430) false once R09 lands. The paragraph should say "not the Switch row's; cognitive's structural projection lives as a named fn beside the table", and the two test/doc sites need correcting at the same time. One author, after R09.Batch:
fix/batch-2026-10-31(#1744 and #1745 first, as their own run, so R09 can rebase onto the landed drivers and drop its/var/tmpcopy).dekobon commentedon Nov 1, 2026
Pointer update: #1744 landed the step this issue carries for it on branch fix/issue-1744 (pending merge; commits 2783e686 and 603e902e, output submodule c51ca7fa). The checklist line above now says so; ir_check.sh and snapdiff.py are recorded there as one-off and not landed.
dekobon commentedon Nov 1, 2026
Follow-ups from R09 (#1699)
Recorded here because they are DSL or close-out questions, not R09's migration (which is zero-drift by contract). Each came out of a CP3 review of
refactor/1699-doc-roles.#![allow(dead_code)]inroles/mod.rscannot narrow after R09: with it off, 58 items the macro emits uniformly on every row are unread —canonical, thepays_*family on owner-only roles, every predicate of anabsentrow. The@namedtable already maps role → module; extend it to role → shape (recognitionfor the R06/R09 roles,payingfor R04/R05's,controlfor R07's) so a recognition row emitsOWNERS,EXCLUDED,TEXT_GATED,owns,owns_kind,lists_kindand nothing else. A consumer reaching forcomment::pays_at_leafthen fails to compile, which is the stronger property.CHAIN_GATED/child_gated_by(correctness + architecture reviews). The byte-less forwards (impl_doc_comment_from_row!,impl_doc_run_directive_from_row!withAncestors::known(&[]);holds_doc_in_body_kind!anddoxygen_entrywith&[]) assert!TEXT_GATEDat build time but nothing pins "no gate reads the chain": a future chain-readingDocCommentgate answersfalsesilently on nested comments, caught only by a fixture that nests one.gated_bycannot distinguish a gate reading the node's own subtree (Rust'shas_doc_marker_child,has_body,field_declares,first_declarator_binds_a_name) from one readingancestors. A third keywordchild_gated_byfor the former, withCHAIN_GATED = any gated_by, lets the forwards assert!CHAIN_GATEDand documents on every row which gates are O(1) on the node.not_under [K…]owner attribute (architecture review). Three R09 gates are the same parent test by hand —python.rs::under_case_clause(pre-existing),go.rs::interface_element_opens_with_a_name'sparent_has_kind(InterfaceType),js_family.rs::under_enum_body— and tsc's export / ambient veto is its complement in the Checker.underexists only on payers.preproc_conditionalreads fivekind()strings per call (CP2 cost audit, cleanup review). The C-family rows now carry every conditional alias id, solang_helpers::doxygen::preproc_conditionalcould be a per-dialectlists_kind-style read;conditional_declaresalso calls it twice per child on the miss path. Constant-factor only (conditionals_readis memoised and equals base).Parameterrow.checker/{typescript,tsx}.rs'safter_codeparameter subset is a hand list underallow(DocTarget)because no row names a parameter kind; decide whether one belongs in the DSL or the bypass stays.HoldsDocInBodyis a residual by name only. The row's definition ("beyond the callables and closures",roles/mod.rs) is policy-flavoured for a crate that names no policy; considerHoldsDocBeyondCallables, or declaring the full set and letting the Checker subtract.#[cfg(any(…))]union per shared helper in each*_testsfile;check'sO(nodes × |expected|)selector; the[bool; N]Answersshape that prints unlabeled on mismatch; shared fixtureconsts for the cpp / mozcpp and javascript / mozjs parity pairs (space_tree_tests.rsdoes this;doc_tests/mod.rscopies).roles/mod.rsmodule doc now carries a "For the documentation epic" section and the reworded role-set table from R09; fold both into the doc rewrite this issue owns rather than restating them..claude/rules/temp-files.md): R09 reused R07'soracle.py/compare.py/recovery_oracle.py/perturb.py(one change:.h→objcin the recovery populations) and added a per-nodedoxygen-oraclecrate that walks error-recovered trees with the real chain and printsChecker::is_doc_targetper node. Four packages have now rebuilt the same drift harness out of tree; this close-out should decide whether it lands underutils/with a*-test.py.dekobon commentedon Nov 4, 2026
R11 commit 1 landed on
fix/issue-1701(63d8ce57): decisions and evidenceDecisions (confirmed with the maintainer 2026-11-04):
is_switch_body_kindbeside the table stays; the convention is documented (a consumer's structural projection of an owner's child lives as a namedfnbeside the table, with a row test pinning the parent/child relation). Since R09, Java'sswitch_blockis claimed byHoldsDocInBody, so the old "no row claims it" wording is corrected; Groovy's was still coverage debt and is nowexcludes [SwitchBlock "…"]on itsSwitchrow (coverage baseline −1, id 215).checker/elixir.rs::is_func_space,getter/elixir.rs::get_space_kind, restatingSourceandAnonymousFunction) are reclassified as three// role-bypass: allowexemptions under the lint's documented subset clause — the byte-less spelling lists the ungated owners of theFuncOpener/Containerrows, the one answer it can give. Not a forward toopens_space:promotes_to_func_space_with_codecalls the byte-less checker spelling per node before its single keyword read, and the forward would run both text-gatedCallclauses (two extraelixir_call_keywordreads percall,calls + defs→3·calls + defsinpromotion_reads_each_calls_keyword_once). Reported as four findings reclassified as three documented subset exemptions, not as four duplicated lists removed. What the exemption no longer sees — a kind added inside either span —every_checker_forward_agrees_with_its_row_or_is_nameddoes on every corpus node (perturbed below).doxygen-oracle): not recoverable — not under/var/tmp/bca/, not in the refactor(cognitive,nexits): declare Branch, Switch, Loop, Catch, ElseIf and Exit #1697 records zip, not attached to refactor(checker): declare DocTarget, DocComment and Docstring rows ahead of the documentation epic #1699. Recorded as one-off; the claim it supported (7,675,318 node lines byte-identical) is on refactor(checker): declare DocTarget, DocComment and Docstring rows ahead of the documentation epic #1699 with its counts, and the derived suite asks theDocTargetrows on the corpus.Zero drift, commit 1. Structural:
git diff origin/main -- src big-code-analysis-ast/src | rg '^[+-][^+-]' | rg -v '^[+-]\s*//'shows theexcludesentry (emits aconst) and test code only. Measured:utils/drift-oracle.py --bca <bca> --out <dir> --lang groovy --lang java --lang elixir --corpus tests/repositories --root cf=tests/repositories/big-code-analysis-output/control_flow --jobs 8on a basebcafromd96b6fca(build inputs1213b4c0…) and a headbcafrom commit 1 (ab739ce4…), both built in the capture step;utils/drift-compare.py→ 0 of 273 outputs differ (21 files, 13 views, 0 key problems).Gates on commit 1:
role-census0 pending;role-coverageOK 5,998 (the stale id-215 entry produced the expectednote:before--update, not a failure);role-bypassOK, 0 baselined sites;role-parity-corpuspass;check-test-lang-gatesOK; ast lib 643/643; rootparity|roles|cognitive|elixir|groovy759/759;.bca-baseline.tomlunchanged afterself-scan-write-baseline-headroom.Perturbations (
utils/perturb.py, in-memory restore,old != newandcount == 1asserted; tree clean after):excludes [SwitchBlock …]groovy::is_else_if_is_the_row_and_the_switch_body_is_the_owners_childmake role-coverageGroovy: kind 215 switch_block … claimed by no role, excluded by none, and not baselinedallow(FuncOpener, Container)from the checker comment,make role-bypasschecker/elixir.rs:31| Elixir::Calladded to the checkermatches!promotion_reads_each_calls_keyword_onceandevery_checker_forward_agrees_with_its_row_or_is_named| Elixir::DoBlockaddedevery_checker_forward_agrees_with_its_row_or_is_named(unnamed disagreement)SwitchBlockremoved from JavaHoldsDocInBodyjava::switch_body_is_the_owners_child_and_not_the_switch_rowsdekobon commentedon Nov 4, 2026
R11 implemented on
fix/issue-1701(five commits,c1506be3; awaiting the PR)9614c25365ee3d24roles/mod.rsloc.slocbaseline refresh, 1789 → 2014)588998cf19fd630ec1506be3Checklist against the body: module doc ✓ (
roles/mod.rs:1-540, nine sections, "What the mechanism cannot see" is the one list);grammar-dispatch.md§1/§4/§5/§7 ✓,macro-comments.mdrow-comment table ✓, AGENTS.md layout bullet + gate paragraph ✓; lesson 115 ✓; bookmetrics.md+new-language.md✓ (README.mduntouched, nothing there names roles); CHANGELOG ✓ (commit 1 carries the code change's entry per #1690 §9, commit 4 the closing entry);pays []mode ✓ (#1745, verified:mode=splitemitted by both readers; the doc names the three modes and quotes no count); Switch-body decision ✓ ((B), recorded in commit 1 and the previous comment); drift drivers ✓ (#1744).Reviews (all findings applied and fixed into their commits): plan-stage
@sol-review(3),@security(3),@astra-review(7); implementation@astra-review×3 (pass 1 on commit 1 + the seven issue bodies: nothing in the commit, six corrections to the bodies; pass 2 on the docs: 7, all precision of claims against source; pass 3 on the final tree: one number), adoc-authorfresh-eyes prose review (25), an under-reviewed-areas + architecture audit (6, incl. the Groovydoc asymmetry now stated onroles/groovy.rs:155-170and #1793 re-sized to 53 references in 17 files).simplify-rust/rust-optimize/ performance audit: n/a — the Rust diff is oneexcludesentry (aconst) and two test assertions.Gates on
c1506be3:make pre-commit→BCA_GATE: pass (gate=pre-commit), 0gpg failed to sign;make check-test-lang-gates-compare COMPARE_REF=origin/mainOK;make role-parity-corpuspass (1,861 files, 83 recovered);role-census0 pending /role-coverage5,998 /role-bypass0;make self-scan-baseline-fresh0/0/0/0;RUSTDOCFLAGS=-D warnings cargo docclean;rumdlclean on the seven.mdfiles; ast lib 645/645.Follow-ups filed outside the series: #1792 (per-role emission profile), #1793 (
child_gated_by/CHAIN_GATED; sized at 53 references in 17 files), #1794 (ownerunder/not_under), #1795 (preproc_conditionalby id), #1796 (Parameterrow — recommended decided-(b), awaiting the maintainer), #1797 (HoldsDocInBodyname), #1798 (role test-file house style). R09'sdoxygen-oraclecrate is recorded as one-off (not recoverable; the claim it supported is on #1699 with counts).The completion comment for #1690 follows the merge.