Skip to content

refactor(jvm-libs): extract config documentation tooling into config-docs module#3606

Open
gauravahuja wants to merge 2 commits into
mainfrom
feat/config-docs-module
Open

refactor(jvm-libs): extract config documentation tooling into config-docs module#3606
gauravahuja wants to merge 2 commits into
mainfrom
feat/config-docs-module

Conversation

@gauravahuja

@gauravahuja gauravahuja commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Reworks the config-documentation tooling per review on #3567 (fluentcrafter): the annotations and engine lived in coordinator/app/src/main, so they shipped in the production jar and could not be reused by Maru.

This PR extracts the reusable engine into a new jvm-libs/linea/config-docs module (package linea.config.docs):

  • ConfigDoc / ConfigSection annotations
  • ConfigKey, ConfigSchemaWalker, ConfigDocValidator
  • ConfigDocJsonGenerator, ConfigDocMarkdownGenerator, ConfigFileRoot
  • unit tests

Key genericity change: section detection is now a SectionDetector parameter (with a sectionByPackagePrefix(...) helper) instead of a hardcoded coordinator package, so other apps (Maru) can supply their own. The generators and walker take the detector.

The moved annotations (Phase 0, #3463) and walker (Phase 1, #3488) are removed from coordinator:app, along with its now-unused explicit kotlin-reflect dependency. App wiring follows in the next PR.

Rework plan

This is PR1 of a 4-PR rework that supersedes #3567 / #3568 / #3588:

  1. This PR — the config-docs module.
  2. Coordinator wiring: compileOnly dep + configDocs source set + check/generate tasks.
  3. Annotate the coordinator config classes (import annotations from the module).
  4. Commit the generated JSON schema + Markdown reference.

Nothing is wired into coordinator:app yet, so behaviour is unchanged there (Phase 0/1 code simply relocated).

Test plan

  • ./gradlew :jvm-libs:linea:config-docs:test — passes.
  • ./gradlew :jvm-libs:linea:config-docs:spotlessCheck :coordinator:app:spotlessCheck — passes.
  • ./gradlew :coordinator:app:compileKotlin :coordinator:app:compileTestKotlin — passes (no dangling references to the moved classes).

Note

Low Risk
Refactor-only: tooling is relocated and generalized with unit tests; coordinator is not yet depending on the new module, so production paths are unaffected.

Overview
Moves Hoplite TOML config documentation out of coordinator:app into a new shared jvm-libs:linea:config-docs library (linea.config.docs), so the engine is not bundled in the coordinator production jar and can be reused (e.g. Maru).

The library holds @ConfigDoc / @ConfigSection, schema walking, completeness validation, JSON/Markdown generators, ConfigDocsRunner, and Gradle entry points driven by a per-app ConfigDocsSpec. Section detection is no longer tied to the coordinator package: callers pass a SectionDetector (with sectionByPackagePrefix for the usual case).

coordinator:app drops the moved sources/tests and the explicit kotlin-reflect dependency; settings.gradle registers the new module. Coordinator Gradle wiring and generated docs are intentionally not in this PR—runtime behavior there is unchanged until follow-ups land.

Reviewed by Cursor Bugbot for commit ef74d31. Bugbot is set up for automated code reviews on this repo. Configure here.

…docs module

Per review on #3567: the config-doc annotations and engine lived in
coordinator/app/src/main, shipping build/analysis tooling in the
production jar and blocking reuse by Maru.

Introduce jvm-libs/linea/config-docs (package linea.config.docs) holding
the reusable engine: ConfigDoc/ConfigSection annotations, ConfigKey,
ConfigSchemaWalker, ConfigDocValidator, and the JSON/Markdown generators,
with unit tests. Section detection is now parameterized via a
SectionDetector (sectionByPackagePrefix helper) instead of a hardcoded
coordinator package, so other apps (Maru) can supply their own.

Remove the moved annotations (Phase 0) and walker (Phase 1) from
coordinator:app and drop its now-unused kotlin-reflect dependency. App
wiring (compileOnly dep + configDocs source set + tasks) follows in the
next PR.

Refs: #3567
Signed-off-by: Gaurav Ahuja <gauravahuja9@gmail.com>
Copilot AI review requested due to automatic review settings July 21, 2026 14:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Extracts the Hoplite TOML config documentation/validation/generation tooling out of coordinator:app into a reusable JVM library module (jvm-libs:linea:config-docs, package linea.config.docs) so other apps can share the same engine without bundling it into the coordinator production artifact set.

Changes:

  • Adds new jvm-libs:linea:config-docs module containing @ConfigDoc/@ConfigSection, ConfigSchemaWalker, ConfigDocValidator, and JSON/Markdown generators, plus unit tests.
  • Refactors section discovery to be pluggable via a SectionDetector (with sectionByPackagePrefix(...)) instead of hardcoding coordinator package logic.
  • Removes the previous coordinator-local tests for these utilities and drops the explicit kotlin-reflect dependency from coordinator:app.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated no comments.

Show a summary per file
File Description
settings.gradle Registers the new jvm-libs:linea:config-docs Gradle module.
jvm-libs/linea/config-docs/build.gradle Defines the new library module dependencies (kotlin-reflect, hoplite-core, jackson-databind).
jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigDoc.kt Moves and generalizes @ConfigDoc / @ConfigSection annotations into the shared module.
jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigKey.kt Moves the ConfigKey model into the shared module and updates docs for section deprecation metadata.
jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigFileRoot.kt Introduces per-config-file root metadata to support multi-file schema/doc generation.
jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigSchemaWalker.kt Moves walker and adds SectionDetector/sectionByPackagePrefix(...) to make section detection app-pluggable.
jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigDocValidator.kt Adds shared validator + deterministic human-readable violation report formatting.
jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigDocJsonGenerator.kt Adds JSON snapshot generator organized per file, with stable key ordering.
jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigDocMarkdownGenerator.kt Adds Markdown reference generator with per-file/per-section tables plus a deprecated-keys appendix.
jvm-libs/linea/config-docs/src/test/kotlin/linea/config/docs/ConfigDocTest.kt Tests runtime readability of @ConfigDoc/@ConfigSection in the new module.
jvm-libs/linea/config-docs/src/test/kotlin/linea/config/docs/ConfigSchemaWalkerTest.kt Tests walker typing/requiredness, map handling, recursion, and undocumented key surfacing using the new SectionDetector.
jvm-libs/linea/config-docs/src/test/kotlin/linea/config/docs/ConfigDocValidatorTest.kt Tests validator rules and deterministic report formatting.
jvm-libs/linea/config-docs/src/test/kotlin/linea/config/docs/ConfigDocGeneratorsTest.kt Tests JSON/Markdown generator shape and determinism.
coordinator/app/build.gradle Removes the explicit kotlin-reflect dependency from the coordinator app.
coordinator/app/src/test/kotlin/linea/coordinator/config/v2/toml/ConfigSchemaWalkerAnnotationTest.kt Removes coordinator-local annotation/walker tests (now covered in config-docs).
coordinator/app/src/test/kotlin/linea/coordinator/config/v2/toml/ConfigDocTest.kt Removes coordinator-local annotation tests (now covered in config-docs).
coordinator/app/src/test/kotlin/linea/coordinator/config/v2/docs/ConfigSchemaWalkerTest.kt Removes coordinator-local walker tests (now covered in config-docs).
coordinator/app/src/test/kotlin/linea/coordinator/config/v2/docs/ConfigKeyRenderingTest.kt Removes coordinator-local key-rendering test coverage (now covered in config-docs).
Comments suppressed due to low confidence (2)

jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigSchemaWalker.kt:106

  • Cycle protection does not treat self-referential section types as a cycle: when sectionClass == kClass and onStack is empty, this will recurse once into the same class and emit nested keys (e.g. node.child.child...) instead of stopping immediately. This is easy to hit if a config data class contains an optional reference to itself.
    coordinator/app/build.gradle:69
  • coordinator:app still directly uses Kotlin reflection extensions (e.g. kotlin.reflect.full.memberProperties / primaryConstructor in coordinator/app/src/main/kotlin/linea/coordinator/config/v2/ConfigLogging.kt), so dropping the explicit kotlin-reflect dependency makes the module rely on a transitive dependency (likely from Hoplite). Other modules in this repo that use Kotlin reflection declare it directly (e.g. maru/core/build.gradle:26, linea-besu/plugins/state-recovery/besu-plugin/build.gradle:13). Consider re-adding an explicit kotlin-reflect dependency here to avoid accidental breakage if transitive deps change.
  implementation "info.picocli:picocli:${libs.versions.picoli.get()}"
  implementation "io.vertx:vertx-web-client"
  implementation "com.sksamuel.hoplite:hoplite-core:${libs.versions.hoplite.get()}"
  implementation "com.sksamuel.hoplite:hoplite-toml:${libs.versions.hoplite.get()}"

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
1241 1 1240 1
View the full list of 1 ❄️ flaky test(s)
maru.app.MaruDiscoveryTest::ten nodes discover each other via bootnode()

Flake rate in main: 34.48% (Passed 19 times, Failed 10 times)

Stack Traces | 119s run time
org.awaitility.core.ConditionTimeoutException: Assertion condition defined as a Lambda expression in maru.app.MaruDiscoveryTest 
Expecting actual:
  8
to be greater than or equal to:
  11
 within 30 seconds.
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:167)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:119)
	at org.awaitility.core.AssertionCondition.await(AssertionCondition.java:31)
	at org.awaitility.core.ConditionFactory.until(ConditionFactory.java:1160)
	at org.awaitility.core.ConditionFactory.untilAsserted(ConditionFactory.java:790)
	at maru.app.MaruDiscoveryTest.testMultiNodeDiscovery-Qn1smSk(MaruDiscoveryTest.kt:200)
	at maru.app.MaruDiscoveryTest.ten nodes discover each other via bootnode(MaruDiscoveryTest.kt:63)
	at java.base/java.lang.reflect.Method.invoke(Method.java:565)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:511)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1450)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2019)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
Caused by: java.util.concurrent.TimeoutException
	at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:206)
	at org.awaitility.core.Uninterruptibles.getUninterruptibly(Uninterruptibles.java:101)
	at org.awaitility.core.Uninterruptibles.getUninterruptibly(Uninterruptibles.java:81)
	at org.awaitility.core.ConditionAwaiter.await(ConditionAwaiter.java:103)
	... 11 more

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copilot AI review requested due to automatic review settings July 21, 2026 14:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigSchemaWalker.kt:106

  • Self-referential section types (a config data class containing a parameter of its own type) will recurse once into itself and duplicate keys because the recursion guard doesn't consider the current class. Including the current class in the guard prevents that edge case and keeps cycle detection consistent.

Add ConfigDocsRunner (walk+validate+report exit code; generate+writeIfChanged
idempotently) plus generic ConfigDocsCheckMain/ConfigDocsGenerateMain entry
points and a ConfigDocsSpec interface. Apps declare a single ConfigDocsSpec
object (roots, section detector, output paths, title) and name it from
build.gradle; the generic mains load it by class name, so apps need no
check/generate entry-point code and the Gradle plugin stays zero-logic.

Refs: #3567
Signed-off-by: Gaurav Ahuja <gauravahuja9@gmail.com>
Copilot AI review requested due to automatic review settings July 23, 2026 09:27
@gauravahuja
gauravahuja force-pushed the feat/config-docs-module branch from 8134ef6 to ef74d31 Compare July 23, 2026 09:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

jvm-libs/linea/config-docs/src/main/kotlin/linea/config/docs/ConfigSchemaWalker.kt:27

  • sectionByPackagePrefix(...) currently uses qualifiedName.startsWith(prefix), which can accidentally match unrelated packages (e.g. prefix linea.config.docs would also match linea.config.docs2.*). Tightening this to require a . boundary after the prefix avoids false-positive section classification.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants