<?xml version="1.0" encoding="UTF-8"?>
<spec xmlns="https://vibevm.org/spec/1">
  <title>GUIDE — Kotlin under the Discipline, v0.1</title>
  <p p="1">**Status.** 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.</p>
  <p p="2">Framing 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.</p>
  <p p="3">**Scope 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.</p>
  <section title="Part I — Core Kotlin (trunk)">
    <section id="baseline" title="0. Language baseline">
      <list ordered="false" p="4">
        <item>**Kotlin 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).</item>
        <item>**`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.</item>
        <item>**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.</item>
        <item>**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.</item>
        <item>**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.</item>
        <item>**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.</item>
        <item>**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.</item>
      </list>
    </section>
    <section id="cells" title="1. Cells">
      <p p="5">A 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.</p>
      <list ordered="false" p="6">
        <item>**Import-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 `val`s and functions); an `object` holding mutable state or doing registration is the singleton the trunk forbids, now with keyword support.</item>
        <item>**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.</item>
        <item>Messages crossing seams are `data class`es of `val`s (or `data object`s); collections crossing seams are read-only types honestly backed.</item>
      </list>
      <p p="7">Cell manifest (annotation carrier, §5):</p>
      <fence lang="kotlin" p="8">@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 { /* ... */ }</fence>
    </section>
    <section id="seams" title="2. Seams">
      <list ordered="false" p="9">
        <item>A seam is an `interface` in the seams module; closed sets are `sealed`. Conformance is nominal — no assertions needed.</item>
        <item>**`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.</item>
        <item>Interface default implementations follow the trunk-wide rule: trivial combinators only, never business flow (R-021).</item>
        <item>`Flow` at seams is **cold**; hot state (`StateFlow`/`SharedFlow`) is owned by a named state holder with an explicit lifetime, exposed read-only.</item>
      </list>
    </section>
    <section id="flags" title="3. Registry and flags">
      <fence lang="kotlin" p="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   -&gt; SatDepSolver(p, clock, io)
        Solver.NAIVE -&gt; NaiveDepSolver(p)
    }</fence>
      <list ordered="false" p="11">
        <item>**Tiers:** 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.</item>
        <item>No `ServiceLoader`-style discovery, no reflection wiring, no DI container in the trunk; Parts II–III legalize containers strictly as composition-root machinery.</item>
      </list>
    </section>
    <section id="errors" title="4. Errors as contract">
      <p p="12">Kotlin 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:</p>
      <list ordered="false" p="13">
        <item>**Seam-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):</item>
      </list>
      <fence lang="kotlin" p="14">sealed 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 }
}</fence>
      <p p="15">`spec` carries the violated REQ URI (PROP-014 §2.6). `kotlin.Result&lt;T&gt;` 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.</p>
      <list ordered="false" p="16">
        <item>**Exceptions = invariant violations** (panic analog). Boundary adapters translate library exceptions into seam errors.</item>
        <item>**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).</item>
        <item>**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.</item>
      </list>
    </section>
    <section id="specmark" title="5. specmark carrier">
      <p p="17">The 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("&lt;uri&gt;", r = N)` — because the *file*, not the package, is Kotlin's organizational unit (no `package-info` needed). ≤3 edges per item or split.</p>
    </section>
    <section id="naming" title="6. Naming (R-020/R-021 bindings)">
      <list ordered="false" p="18">
        <item>Computed `{Variant}{Seam}` → `SatDepSolver`; linted against the manifest.</item>
        <item>**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).</item>
      </list>
    </section>
    <section id="replacement" title="7. Replacement protocol (R-040 binding)">
      <p p="19">Differential 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.</p>
    </section>
    <section id="risks" title="8. Risk table — trunk">
      <table p="20">
        <tr>
          <td>Footgun</td>
          <td>Rule</td>
          <td>Tier</td>
        </tr>
        <tr>
          <td>side effects in `object`/`companion`/top-level initializers</td>
          <td>§1</td>
          <td>T-syn</td>
        </tr>
        <tr>
          <td>mutable state in an `object`; companion registry</td>
          <td>§1, §6</td>
          <td>T-syn</td>
        </tr>
        <tr>
          <td>hardcoded `Dispatchers.*` in a cell</td>
          <td>§1</td>
          <td>T-syn</td>
        </tr>
        <tr>
          <td>`GlobalScope`; `runBlocking` outside main/tests; scope-leaking coroutine</td>
          <td>§4</td>
          <td>T-syn</td>
        </tr>
        <tr>
          <td>broad catch swallowing `CancellationException`</td>
          <td>§4</td>
          <td>T-syn (detekt)</td>
        </tr>
        <tr>
          <td>`else` branch on a sealed `when`; statement-`when` over sealed</td>
          <td>§4</td>
          <td>T-syn</td>
        </tr>
        <tr>
          <td>`kotlin.Result` in a seam signature</td>
          <td>§4</td>
          <td>T-syn</td>
        </tr>
        <tr>
          <td>`!!` / `lateinit` in a cell; unpinned platform type crossing inward</td>
          <td>§0</td>
          <td>T-syn + T-sem</td>
        </tr>
        <tr>
          <td>DSL builder / operator magic / side-effectful delegate in domain code</td>
          <td>§6</td>
          <td>T-syn</td>
        </tr>
        <tr>
          <td>`kotlin-reflect` in a cell</td>
          <td>§6</td>
          <td>T-lex</td>
        </tr>
        <tr>
          <td>MockK/Mockito in a cell test</td>
          <td>§0</td>
          <td>T-syn</td>
        </tr>
        <tr>
          <td>`expect`/`actual` encoding a product variant</td>
          <td>§3</td>
          <td>review + T-syn</td>
        </tr>
        <tr>
          <td>missing/changed `.api` dump without rationale</td>
          <td>§0</td>
          <td>build diff</td>
        </tr>
        <tr>
          <td>flag read outside the registry</td>
          <td>R-001</td>
          <td>T-syn</td>
        </tr>
        <tr>
          <td>public export without own/inherited spec edge</td>
          <td>PROP-014 §3.2-6</td>
          <td>T-syn + index</td>
        </tr>
      </table>
    </section>
    <section id="docs" title="9. Doc layer">
      <p p="21">KDoc + 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.</p>
    </section>
  </section>
  <section title="Part II — Server &amp; Desktop (area overlay)">
    <p p="22">**Inherits the trunk; JVM target composes with the Java ecosystem.**</p>
    <list ordered="false" p="23">
      <item>**ArchUnit 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.</item>
      <item>**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.</item>
      <item>**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.</item>
      <item>**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.</item>
      <item>**Desktop:** Compose for Desktop is the JVM target of Part III's Compose rules — same state-hoisting discipline, same effect rules; packaging via jpackage.</item>
    </list>
    <table p="24">
      <tr>
        <td>Additional risks</td>
        <td>Rule</td>
        <td>Tier</td>
      </tr>
      <tr>
        <td>all-open applied to cell classes</td>
        <td>II</td>
        <td>build config audit</td>
      </tr>
      <tr>
        <td>Spring/Ktor type in a seam signature</td>
        <td>II</td>
        <td>ArchUnit</td>
      </tr>
      <tr>
        <td>`runBlocking` inside request handling</td>
        <td>II</td>
        <td>T-syn</td>
      </tr>
    </table>
  </section>
  <section title="Part III — Kotlin Multiplatform: Android &amp; iOS, Compose (area overlay)">
    <p p="25">**The 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.</p>
    <list ordered="false" p="26">
      <item>**`expect`/`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.)</item>
      <item>**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.</item>
      <item>**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.</item>
      <item>**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 `val`s; `@Immutable` where inference needs help). Screenshot tests are goldens under the promotion protocol.</item>
    </list>
    <table p="27">
      <tr>
        <td>Additional risks</td>
        <td>Rule</td>
        <td>Tier</td>
      </tr>
      <tr>
        <td>platform API named in commonMain</td>
        <td>III</td>
        <td>compiler</td>
      </tr>
      <tr>
        <td>domain dependency via `CompositionLocal`</td>
        <td>III</td>
        <td>T-syn</td>
      </tr>
      <tr>
        <td>side effect in composition outside effect handlers</td>
        <td>III</td>
        <td>T-syn (compose lints)</td>
      </tr>
      <tr>
        <td>business logic in a composable; stateful composable where hoisting fits</td>
        <td>III</td>
        <td>review + T-syn</td>
      </tr>
      <tr>
        <td>`GlobalScope`/unowned scope in a ViewModel</td>
        <td>trunk §4</td>
        <td>T-syn</td>
      </tr>
      <tr>
        <td>raw `suspend` exported to Swift</td>
        <td>III</td>
        <td>export audit</td>
      </tr>
    </table>
  </section>
  <section title="Part IV — Kotlin/JS (area overlay)">
    <p p="28">**The 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:</p>
    <list ordered="false" p="29">
      <item>**`@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.</item>
      <item>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.</item>
      <item>**`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`).</item>
      <item>npm dependencies are locked (`kotlin-js-store` committed — A2); IR backend + ES modules assumed.</item>
      <item>**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.</item>
    </list>
    <table p="30">
      <tr>
        <td>Additional risks</td>
        <td>Rule</td>
        <td>Tier</td>
      </tr>
      <tr>
        <td>`dynamic` in a cell</td>
        <td>IV</td>
        <td>T-syn</td>
      </tr>
      <tr>
        <td>`Long`/raw `suspend`/non-exportable type in `@JsExport` surface</td>
        <td>IV</td>
        <td>compiler + export audit</td>
      </tr>
      <tr>
        <td>uncommitted lockfile or `.d.ts` drift</td>
        <td>IV</td>
        <td>build diff</td>
      </tr>
    </table>
    <p p="31">**First 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.</p>
  </section>
</spec>
