Skip to content

test: enforce architecture rules with Konsist (no java/android imports in commonMain, public-API allowlist, etc.) #29

@jamesarich

Description

@jamesarich

Observation

Your AGENTS.md <rules> block lists a bunch of strict invariants — no java.*/android.*/platform APIs in commonMain, an explicit ~13-type allowlist for public declarations (MqttClient, MqttConfig, MqttMessage, PublishProperties, MqttEndpoint, QoS, ConnectionState, ReasonCode, WillConfig, MqttLogger, MqttLogLevel, RetainHandling, AuthChallenge + the named convenience extensions), MqttTransport/MqttProperties internal, zero deps beyond Ktor + coroutines + kotlinx-io, etc.

You already have a great foundation: library/src/jvmTest/kotlin/org/meshtastic/mqtt/architecture/ArchitectureTest.kt covers 5 of these via Konsist (no java./javax./android./platform. imports in commonMain, MqttTransport internal, MqttProperties internal). Nice — that already catches the most common drift class that apiCheck can't see.

This issue is just to suggest closing the remaining gap between the prose rules and the mechanical ones, and a couple of small structural tweaks.

Proposal

1. Move (or duplicate) the suite into commonTest

Konsist's Konsist.scopeFromProduction() reads source files, not compiled classes, so it works fine from commonTest on a KMP project. Putting the suite in commonTest means it runs as part of allTests on every CI matrix entry, not only jvmTest. That's mostly belt-and-braces — the rules are source-level — but it makes the architectural contract a property of the library rather than of the JVM target.

(If there's a reason it's intentionally jvmTest-only — e.g. CI cost — totally fair, ignore this part.)

2. Additional rules from AGENTS.md that translate cleanly

Each of these maps to a Konsist one-liner against scopeFromProduction() — see the Konsist snippet inspiration page for close analogues:

  • Public-API allowlist. Every top-level public declaration in commonMain must be in the named set above (plus the messagesForTopic / messagesMatching / use extensions and the MqttClient(clientId) {} factory). Today this is enforced by apiCheck at the symbol level — but apiCheck only fires once a leak ships in the dump. A Konsist test can fail on the introducing PR with a clearer error message ("FooHelper was made public; either add it to the allowlist in ArchitectureTest or mark it internal").
  • MqttPacket and all its subtypes are internal. The sealed interface plus all 15 packet implementations.
  • All packet classes are data class (or data object). Catches accidental class / object declarations under the packet sealed hierarchy.
  • All data class packet fields are val. Konsist's properties().assertTrue { it.hasValModifier } filtered to the packet hierarchy.
  • (Optional) No println / System.err.println in commonMain or nonWebMain production sources — the repo already calls this out implicitly via the MqttLogger abstraction.

3. Implementation notes

  • Konsist (com.lemonappdev:konsist:0.17.3) is already in your version catalog and on the jvmTest classpath, so the only build change to move to commonTest is shifting the testImplementation(libs.konsist) dependency from the jvm test source set to commonTest.
  • Single suite typically runs in 1–3s; not a CI cost concern.
  • For the public-API allowlist test, keeping the allowlist as a private val ALLOWED = setOf("MqttClient", …) inside the test file means contributors get a one-line diff to review when surface intentionally grows.
  • Konsist works on KMP projects today and operates on the entire source tree from commonTest. Their docs cover the KMP setup directly: https://docs.konsist.lemonappdev.com.

4. Offer

Happy to send a starter PR with the four highest-value rules (public-API allowlist, MqttPacket internal, packet data class-ness, packet-field immutability) if it'd help. Just say the word and I'll target a fork of main.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions