VibeVM
Contents
On this page
en
Publisher
org.vibevm.ai-native
Version
1.0.0latest
Audiences
Reading time
10 min
Rendered
Read aloud
never

GUIDE — Kotlin under the Discipline, v0.1

01Status. Beta; seventh language. One document by the owner's structure: Part I is the trunk (core Kotlin), Parts II–IV are inline area overlays that inherit it and rewire only named sections. The trunk is sovereign — Kotlin Multiplatform makes the JVM just one target, so this is not an overlay on the Java trunk; instead, Part II composes with the Java ecosystem overlays (Spring) at the JVM, and Part IV defines the Discipline's first inter-trunk boundary (Kotlin/JS ↔ GUIDE-TYPESCRIPT). Sections stay isomorphic to the other guides.

02Framing note — the seventh point of the typology. Rust enforces; TS permits but compiles; Python trusts; C++ demands a subset; Go prescribes; Java bifurcates; Kotlin inverts the defaults: everything Java left dangerous-by-default is opt-in here — null, mutability, inheritance (final by default), and in Multiplatform even platform access. Where the defaults already match the axioms, this guide is thin, like Go's. The gaps it closes: the vanished failure surface (checked exceptions removed and replaced with nothing at the signature level); the GlobalScope escape hatch in an otherwise natively-structured concurrency model; the DSL/operator culture (the language's superpower is also its R-021 risk zone); and platform types at the Java border. Patterns dissolve far: Strategy = interface + registry; Visitor = sealed + exhaustive when; Observer = Flow seam; Singleton = a keyword (object) — first-class syntax for the forbidden thing, governed below.

03Scope honesty. Long-lived system/product code. Gradle build scripts (themselves Kotlin DSL) are tooling, not cells. Code generated by KSP processors follows the generated-code rule: processor input is the taggable unit; processors generating domain logic are infra-only.

Part I — Core Kotlin (trunk)

0. Language baseline

  • 04Kotlin 2.1+ (K2 compiler), target latest stable. Gradle with version catalogs + convention plugins — the ecosystem reality (KMP is Gradle-only); Maven legal for pure-JVM targets. Dependency locking on (A2).
  • allWarningsAsErrors = true. explicitApi() is MUST for cell modules — explicit visibility and return types on every public declaration: surface honesty as a compiler flag, not a review note.
  • Surface ratchet: kotlinx-binary-compatibility-validator (Apache-2.0) — committed .api dumps, diffed in PRs; an unexplained surface change is a finding before it is an API break.
  • Lint gate: detekt (Apache-2.0) — and its baseline file is a native brownfield ratchet (BROWNFIELD §B1 shipped inside the tool: existing findings frozen, new ones fail, the file only shrinks). Formatting via one pinned formatter (ktlint/ktfmt) — zero discipline budget.
  • Suppression policy (xfail-strict posture): @Suppress only with the narrowest scope, the specific check name, and a // reason; a conform sweep re-runs named checks against suppressed elements and fails stale entries.
  • Null discipline: !! is banned in cells (the assert-shaped escape hatch); lateinit is banned in cells (two-phase-init smell — constructor injection removes the need); platform types (T!) are quarantined: every value arriving from Java interop is pinned to explicit nullability at the boundary before it travels. val is the default; var is justified.
  • Tests: kotlin-test in common code; JUnit 5 on JVM (EPL-2.0 flag inherited from the Java trunk); property testing via kotest-property (Apache-2.0) — a license upgrade over Java's EPL-flagged jqwik. MockK and Mockito are banned in cell tests — same rule, same reason as every sibling guide: capability injection makes module-graph mocking unnecessary.

1. Cells

05A cell is a Gradle module (in KMP, its commonMain source set — Part III makes that physics). explicitApi + the .api dump define its surface; promotion/extraction on the usual triggers.

  • 06Import-is-execution, Kotlin edition: object and companion object initialize lazily on first touch; top-level property initializers run at file-class init. Side effects there are banned in cells. object is legal only as a pure namespace (stateless vals and functions); an object holding mutable state or doing registration is the singleton the trunk forbids, now with keyword support.
  • Capabilities are injected, including the ones Kotlin is famous for forgetting: CoroutineDispatcher/CoroutineContext is an injected capability — hardcoded Dispatchers.IO/Dispatchers.Main in a cell is a finding (and the reason cell tests never need a main-dispatcher rule); Clock (kotlinx-datetime) injected; filesystem/network/env never ambient.
  • Messages crossing seams are data classes of vals (or data objects); collections crossing seams are read-only types honestly backed.

07Cell manifest (annotation carrier, §5):

08@SpecImplements("spec://org.vibevm.core/vibevm/modules/vibe-resolver/PROP-003#solver-upgrade", r = 2)
@Cell(seam = "DepSolver", variant = "sat", replaces = "naive", flag = "solver")
class SatDepSolver(
    private val provider: DepProvider,
    private val clock: Clock,
    private val dispatcher: CoroutineDispatcher,
) : DepSolver { /* ... */ }

2. Seams

  • 09A seam is an interface in the seams module; closed sets are sealed. Conformance is nominal — no assertions needed.
  • suspend functions are the async seam form. This is a declared divergence from the Java trunk (which prescribes blocking seams on virtual threads): each trunk follows its platform's structured-concurrency culture, and at a mixed JVM boundary a one-function bridge (runBlocking/Dispatchers adapter) converts — the divergence is owned, not hidden.
  • Interface default implementations follow the trunk-wide rule: trivial combinators only, never business flow (R-021).
  • Flow at seams is cold; hot state (StateFlow/SharedFlow) is owned by a named state holder with an explicit lifetime, exposed read-only.

3. Registry and flags

10// registry module — the only flag reader and the only module depending on cells.
fun depSolver(cfg: AppConfig, p: DepProvider, clock: Clock, io: CoroutineDispatcher): DepSolver =
    when (cfg.solver) {                       // provenance: default | env | cli | lockfile
        Solver.SAT   -> SatDepSolver(p, clock, io)
        Solver.NAIVE -> NaiveDepSolver(p)
    }
  • 11Tiers: assembly tier = which modules (and, in KMP, which targets) are built — "is the code in the artifact"; runtime tier = a config data class bound once in main. expect/actual never encodes product variants — it is platform adaptation only; a product flag wearing expect clothing is a finding.
  • No ServiceLoader-style discovery, no reflection wiring, no DI container in the trunk; Parts II–III legalize containers strictly as composition-root machinery.

4. Errors as contract

12Kotlin removed Java's checked exceptions — deliberately — and put nothing in the signature where they stood: exceptions here are unchecked and undeclared, the most vanished failure surface in the set. The Discipline restores it the same way as everywhere:

  • 13Seam-owned sealed results, consumed by exhaustive when as an expression, with no else branch — a new permitted subtype must break every consumer's build (the default-arm ban, fourth appearance):
14sealed interface SolveResult {
    data class Ok(val resolution: Resolution) : SolveResult
    data class Err(val error: SolveError) : SolveResult
}
data class SolveError(val code: Code, val spec: String, val cause: Throwable? = null) {
    enum class Code { CYCLE, UNSATISFIABLE }
}

15spec carries the violated REQ URI (PROP-014 §2.6). kotlin.Result<T> is not a seam type — its error channel is pinned to Throwable, which is an implementation genus, not a domain contract. Arrow's Either/Raise (Apache-2.0) is parked exactly as Effect-TS was: an optional profile for FP-shaped carriers, not the trunk binding.

  • 16Exceptions = invariant violations (panic analog). Boundary adapters translate library exceptions into seam errors.
  • Cancellation sanctity — the №1 coroutine footgun: a broad catch (e: Exception) silently eats CancellationException and breaks structured cancellation; every broad catch MUST rethrow it (detekt rule + conform check).
  • Structured concurrency is native — keep it that way: GlobalScope is banned (the language's one escape hatch from its own best idea); runBlocking only at main/test edges; scopes are owned (coroutineScope/supervisorScope or an injected scope with a named lifetime); a coroutine that outlives its cell is a leak by definition.

5. specmark carrier

17The same specmark annotations as Java, defined once in commonMain and visible to every target: AnnotationRetention.BINARY (in the artifact for analysis, invisible to runtime reflection), @Repeatable, @MustBeDocumented — edges render in Dokka, so the carrier doubles as the doc surface. File-level scope uses Kotlin's native syntax — @file:SpecScope("<uri>", r = N) — because the file, not the package, is Kotlin's organizational unit (no package-info needed). ≤3 edges per item or split.

6. Naming (R-020/R-021 bindings)

  • 18Computed {Variant}{Seam}SatDepSolver; linted against the manifest.
  • The Kotlin theater list — placement, not amputation: the language's DSL powers (lambda-with-receiver builders, infix, operator overloading) are banned in domain cells and legal where they shine — boundaries, configuration, test fixtures (Gradle and Ktor routing are the canonical homes). Also banned in cells: operator overloading beyond value semantics; delegated properties with side effects (by lazy of a pure value is fine; observable/vetoable is hidden control flow); extension functions on foreign types sprayed outside the owning cell; kotlin-reflect; companion-object registries; !!/lateinit (§0).

7. Replacement protocol (R-040 binding)

19Differential oracle via kotest-property driving both cells through the seam (@SpecVerifies-tagged), divergences documented. Goldens follow the promotion protocol. On JVM the Java trunk's strict @KnownFailing JUnit extension carries over as the in-source twin of tests-baseline.json; in common tests, the baseline registry carries the weight alone (Go's situation) — stated, not hidden.

8. Risk table — trunk

20
Footgun Rule Tier
side effects in object/companion/top-level initializers §1 T-syn
mutable state in an object; companion registry §1, §6 T-syn
hardcoded Dispatchers.* in a cell §1 T-syn
GlobalScope; runBlocking outside main/tests; scope-leaking coroutine §4 T-syn
broad catch swallowing CancellationException §4 T-syn (detekt)
else branch on a sealed when; statement-when over sealed §4 T-syn
kotlin.Result in a seam signature §4 T-syn
!! / lateinit in a cell; unpinned platform type crossing inward §0 T-syn + T-sem
DSL builder / operator magic / side-effectful delegate in domain code §6 T-syn
kotlin-reflect in a cell §6 T-lex
MockK/Mockito in a cell test §0 T-syn
expect/actual encoding a product variant §3 review + T-syn
missing/changed .api dump without rationale §0 build diff
flag read outside the registry R-001 T-syn
public export without own/inherited spec edge PROP-014 §3.2-6 T-syn + index

9. Doc layer

21KDoc + Dokka on every public declaration (explicitApi already forces you to notice them): behavior, seam error codes with REQ URIs, nullability beyond the types, coroutine context expectations (which dispatcher, cancellation behavior), Flow temperature. @MustBeDocumented specmark edges render alongside. Spec stays thin.

Part II — Server & Desktop (area overlay)

22Inherits the trunk; JVM target composes with the Java ecosystem.

  • 23ArchUnit rules apply verbatim — it reads bytecode and does not care that the source was Kotlin; the ring rules ("cells import no framework") transfer without translation. Error Prone does not apply (javac-only); detekt carries the lint weight.
  • Spring composition: GUIDE-JAVA-SPRING applies, with Kotlin clauses: the kotlin-spring/all-open compiler plugin is scoped to boundary stereotypes only — cells stay final (final-by-default is a Discipline ally; opening a cell for proxying is a finding). Constructor injection is the language's natural mode; @ConfigurationProperties binds to data classes. The concurrency divergence resolves per carrier, documented once: suspend seams with coroutine-aware boundary (WebFlux/MVC-with-suspend) or the Java trunk's Loom stance with bridges — one model per repository.
  • Ktor (Apache-2.0) is the Kotlin-native boundary framework; its routing DSL is the placement rule in action — DSL at the boundary, plain calls inward.
  • Persistence: Exposed/SQLDelight-class tools live at the boundary; SQLDelight's model (typed APIs generated from SQL) fits the generated-code rule cleanly — the .sq files are the taggable inputs.
  • Desktop: Compose for Desktop is the JVM target of Part III's Compose rules — same state-hoisting discipline, same effect rules; packaging via jpackage.
24
Additional risks Rule Tier
all-open applied to cell classes II build config audit
Spring/Ktor type in a seam signature II ArchUnit
runBlocking inside request handling II T-syn

Part III — Kotlin Multiplatform: Android & iOS, Compose (area overlay)

25The headline: commonMain is the cell ring made physics. Common code cannot name a platform API — no android.*, no UIKit, no DOM exist in its classpath; the capability-injection rule every other guide enforces with linters is enforced here by the compiler's world model. Cells live in commonMain; androidMain/iosMain are boundary adapters.

  • 26expect/actual is the platform-capability seam — narrow declarations (clock, storage, secure random), actuals are adapters. Business seams remain ordinary interfaces in common code. (Product variants never wear expect — trunk §3.)
  • iOS: modern Kotlin/Native memory model assumed (no freeze-era folklore). The exported surface is an XCFramework governed by explicitApi + the .api dump; Swift/ObjC interop is boundary work, suspend crosses to Swift only through generated bridges/completion adapters at the boundary, never raw.
  • Android: Context is the platform god-object and physically cannot enter commonMain — the architecture problem Android spent a decade on, dissolved by source-set geometry. Lifecycle, permissions, services: boundary. DI: Hilt is compile-time codegen (A3-friendlier than runtime containers) and is confined to the boundary ring; Koin (runtime locator) likewise if a carrier insists; cells are new-constructed inside producers either way.
  • Compose — the framework that agrees: Compose's own law is that composition must be side-effect-free — the import-is-execution instinct, adopted by a UI runtime. Bindings: state hoisting is capability injection for UI — stateless composables take state and event lambdas; business logic never lives in a composable; side effects only in LaunchedEffect/DisposableEffect with honest keys; CompositionLocal is the http.DefaultClient of Compose — banned for domain dependencies, legal for theme/density as designed; remember { } holds pure values. State holders/ViewModels are boundary orchestration talking to seams. UI models crossing into composition are stable by construction (data classes of vals; @Immutable where inference needs help). Screenshot tests are goldens under the promotion protocol.
27
Additional risks Rule Tier
platform API named in commonMain III compiler
domain dependency via CompositionLocal III T-syn
side effect in composition outside effect handlers III T-syn (compose lints)
business logic in a composable; stateful composable where hoisting fits III review + T-syn
GlobalScope/unowned scope in a ViewModel trunk §4 T-syn
raw suspend exported to Swift III export audit

Part IV — Kotlin/JS (area overlay)

28The Discipline's first inter-trunk boundary. A Kotlin/JS artifact consumed by TypeScript code is a border between two guides of the same Discipline — GUIDE-KOTLIN on one side, GUIDE-TYPESCRIPT on the other. The contract is explicit:

  • 29@JsExport is the seam surface — minimal, deliberate, and the generated .d.ts is committed and diffed: a cross-language surface ratchet, the .api dump's twin. TS-side conform treats the artifact as a typed boundary module; Kotlin-side conform audits the export set.
  • Exported signatures avoid the classic traps: no Long across the border (JS numbers), no raw suspend (Promise adapters at the boundary), collections crossed as arrays/readonly shapes.
  • dynamic is banned in cells — it is Kotlin's any, quarantined to boundary interop; external declarations are the typed border to JS libraries (the inward-facing .d.ts).
  • npm dependencies are locked (kotlin-js-store committed — A2); IR backend + ES modules assumed.
  • Release map, double hop: Kotlin/JS emits source maps, so the TS guide's chain extends — minified frame → JS → Kotlin source → item → REQ; ship/retain both map layers keyed by build id. Kotlin/Wasm: not baseline yet; named, not used.
30
Additional risks Rule Tier
dynamic in a cell IV T-syn
Long/raw suspend/non-exportable type in @JsExport surface IV compiler + export audit
uncommitted lockfile or .d.ts drift IV build diff

31First carrier note. No Kotlin exists in vibevm; no carrier is designated. Trunk and areas ship genre-complete and unexercised under the carrier-relative house clause: rules remain DRAFT until a first Kotlin carrier exists, and at its first milestone any rule without a conform check (or explicit WISH mark) is removed rather than carried as aspiration.

For an agent

This page has a machine mirror. The citation carries the version rather than latest, so what an agent quotes does not move under it.

spec://org.vibevm.ai-native/core-ai-native@1.0.0/legacy-projections/GUIDE-KOTLIN-v0.1

.md.xmlllms.txt