# elide.pkl reference

> Every block and field of Elide's elide.pkl project manifest: metadata, sources, dependencies, JVM, Kotlin, JavaScript, artifacts, testing and workspaces.

`elide.pkl` is the project manifest: it declares builds, dependencies, tests and artifacts. Every file starts with:

```pkl
amends "elide:project.pkl"
```

`elide:` resolves to the schema bundled with the running Elide, for this and every other module (`elide:Jvm.pkl`).
To pin a schema version, amend the published URL:
[`pkl.elide.dev/1.6.3/project.pkl`](https://pkl.elide.dev/1.6.3/project.pkl).

Schema versions are independent of the Elide runtime version. A development schema can be locked before it is
published; the URL above stays on a published version. Published development versions follow `main` until their
stable release, after which they are immutable.

Patch releases within a `MAJOR.MINOR` line only add properties. A minor release can remove, rename or retype
properties and change defaults.

| Pinned version | Result |
| --- | --- |
| Older than the bundled schema | Uses that version; a property removed since fails, naming the version. |
| Newer than the bundled schema | Allowed; a property the running Elide lacks fails, naming the upgrade. |
| Another major version | Refused. |

`elide project export` prints the evaluated manifest as JSON; `elide build --inspect` lists its tasks.

## Project metadata

| Property | Purpose |
| --- | --- |
| `name`, `version`, `description` | Project identity; `version` also names application archives. |
| `entrypoint` | File(s) `elide run` uses when given none. |
| `scripts` | Named shell commands for `elide run NAME`. |
| `sources` | Source sets; see [Sources](#sources). |
| `dependencies` | Maven, npm and PyPI packages; see [Dependencies](#dependencies). |
| `jvm`, `kotlin`, `javascript` | Language and compiler settings. |
| `nativeImage` | Accepted and not applied; set each artifact's `options`. |
| `engine` | Serving contexts; see [Engine contexts](#engine-contexts). |
| `toolchain` | Accepted Elide versions; see [Toolchain](#toolchain). |
| `dev` | Source info, MCP, dev server; see [Dev](#dev). |
| `web` | Web asset settings; see [Web](#web). |
| `artifacts` | Build outputs; see [Artifacts](#artifacts). |
| `testing` | Test reports; see [Testing](#testing). |
| `coverage` | Coverage; see [Testing › Coverage](https://elide.help/docs/toolchain/testing#coverage). |
| `workspace` | Member projects; see [Workspaces](#workspaces). |

`build.flags` holds the `-f`/`--flag` values; see [Build Flags](https://elide.help/docs/toolchain/build-flags).

## Engine contexts

```pkl
engine {
  topology = "prespawn"
  contexts { spawn = "auto"; reserve = 2; min = 1; max = 16 }
}
```

| Property | Meaning | Default |
| --- | --- | --- |
| `topology` | `"prespawn"`, the only value. | `"prespawn"` |
| `contexts.spawn` | Context count: a positive integer, or `"auto"` (physical cores minus `reserve`, clamped). | `1` |
| `contexts.reserve` | Cores subtracted for `"auto"`; does not pin CPUs. | `2` |
| `contexts.min`, `contexts.max` | Bounds for any count. | `1`, none |
| `maxContexts` | Deprecated fixed count; cannot combine with `contexts`. | — |

Each context evaluates the entry module once, with its own globals, before the server is ready.
`--contexts=N|auto` on `run`, `serve` and `dev` overrides `spawn`. More than one context requires Linux.

## OpenTelemetry block

```pkl
opentelemetry {
  service { name = "checkout"; version = "1.4.2"; environment = "production" }
  resource { ["cloud.region"] = "us-east-1" }

  destinations {
    ["collector"] { endpoint = "http://localhost:4317"; protocol = "grpc" }
    ["datadog"] { provider = "datadog"; site = "us5" }
    ["sentry"] { provider = "sentry" }
  }

  traces { sampler = "parentbased_traceidratio"; ratio = 0.25 }
  metrics { interval = 30.s }
  logs { level = "info" }
  runtime { logs = "warn" }
}
```

Telemetry is off until a destination exists here, in `OTEL_*`/`ELIDE_OTEL_*` variables, or in `--otel-*`
flags. Those override this block, flags first; see [Runtime Environment Variables](https://elide.help/docs/runtime-env-variables#opentelemetry-variables).
`elide run`, `elide serve`, `elide test`, `elide build` and `elide install` in a project read this block; `elide run file.js`
inside a project reads it when `elide.pkl` contains the word `opentelemetry`. See [Configuration](https://elide.help/docs/opentelemetry/configuration). Credentials are never written here: name the environment variable that holds them.

| Property | Meaning | Default |
| --- | --- | --- |
| `enabled` | `false` turns telemetry off, whatever the environment says. | — |
| `service.name`, `.version`, `.namespace`, `.environment` | `service.*` and `deployment.environment.name` resource attributes. | `unknown_service:elide` |
| `resource` | Extra resource attributes. | — |
| `destinations` | Named places to send telemetry. | none |
| `traces.enabled`, `.sampler`, `.ratio`, `.propagators` | Application spans. Samplers as `OTEL_TRACES_SAMPLER`. | on, `parentbased_always_on`, `tracecontext` + `baggage` |
| `metrics.enabled`, `.interval`, `.temporality` | Application metrics. | on, `60.s`, `cumulative` |
| `logs.enabled`, `.level`, `.console` | Application logs; `console` exports `console.*`, Python `logging` and similar output. | on, `info`, on |
| `profiles.enabled`, `.frequency` | Guest CPU profiles (experimental). | off, 100 per second |
| `runtime.traces`, `.metrics`, `.logs` | Elide's own spans, metrics, and log threshold. | on, on, `warn` |
| `propagation.baggage` | W3C baggage on Elide's HTTP server and clients; see [Baggage](#baggage). | off |
| `shutdownTimeout` | Final export deadline at exit. | `5.s` |

Each destination:

| Property | Meaning | Default |
| --- | --- | --- |
| `provider` | `otlp`, `sentry`, `datadog`, `posthog`, `honeycomb`, or `grafana`. | `otlp` |
| `endpoint` | Base URL; replaces a provider's host. `/v1/<signal>` is appended for HTTP. | `http://localhost:4318` (`4317` for gRPC) |
| `protocol` | `grpc`, `http/protobuf`, or `http/json`. | `http/protobuf` |
| `headers`, `headerEnv` | Literal headers, and headers read from named variables. | — |
| `credential` | Variable holding the provider's key: `DD_API_KEY`, `POSTHOG_PROJECT_TOKEN`, `HONEYCOMB_API_KEY`, `GRAFANA_CLOUD_OTLP_CREDENTIAL`. | as listed |
| `dsn` | Variable holding a Sentry DSN. | `SENTRY_DSN` |
| `site` | Datadog site: `us1`, `us3`, `us5`, `eu`, `ap1`, `ap2`, `gov`, or a domain. | `DD_SITE`, then `us1` |
| `region` | `us`/`eu` (PostHog, Honeycomb), or a Grafana Cloud zone such as `prod-us-east-0`. | `us` |
| `signals` | `traces`, `metrics`, `logs`, `profiles`. | what the provider accepts |
| `compression`, `timeout` | Request compression and deadline. | `gzip`, `10.s` |
| `perSignal` | Per-signal `endpoint`, `protocol`, `headers`, `headerEnv`, `compression`, `timeout`. | — |

| Provider | Signals sent by default | Also accepted | Protocols |
| --- | --- | --- | --- |
| `otlp` | traces, metrics, logs | profiles | all |
| `sentry` | traces, logs | — | `http/protobuf` |
| `datadog` | metrics (delta), logs | traces (agentless intake preview) | `http/protobuf`; `http/json` except logs |
| `posthog` | logs | traces | `http/protobuf` |
| `honeycomb` | traces, metrics, logs | — | all |
| `grafana` | traces, metrics, logs | — | `http/protobuf` |

Errors are sent as `exception` log records and span events, plus the `elide.errors` metric, so every provider that
accepts logs, traces, or metrics receives them. See [OpenTelemetry](https://elide.help/docs/opentelemetry) for usage and [Providers](https://elide.help/docs/opentelemetry/providers) for each provider. Asking a provider for a signal or protocol it does not accept is an error.

### Baggage

Elide's HTTP server and clients propagate `traceparent` and `tracestate`. They pass W3C `baggage` only when you turn a
direction on, because baggage arriving from a client is untrusted input:

```pkl
opentelemetry {
  propagation {
    baggage {
      inbound = true
      outbound = true
      allow { "tenant"; "region" }
      spanAttributes { "tenant" }
    }
  }
}
```

| Property | Meaning | Default |
| --- | --- | --- |
| `inbound` | Served requests' `baggage` headers become the handler's baggage. | `false` |
| `outbound` | Outgoing `fetch` and HTTP client requests carry the current baggage, unless the request sets `baggage` itself. | `false` |
| `allow` | Keys allowed in both directions. | all keys |
| `spanAttributes` | Inbound keys recorded on the server span as `baggage.<key>`; each must be in `allow`. | none |

A header over 8,192 bytes or 180 members, or with any malformed member, is dropped whole. Baggage received from a
client reaches outgoing requests only when both directions are on. Application code can always read, set and send
baggage through the OpenTelemetry API; `OTEL_PROPAGATORS` affects only that API.

## Sources

```pkl
sources {
  ["main"] = "lib/**/*.kt"
  ["integrationTest"] {
    type = "test"
    dependsOn { "main" }
    paths { "it/**/*.kt" }
    resources { "it/fixtures"; new { from = "it/config"; into = "META-INF" } }
  }
}
```

| Property | Meaning | Empty or absent |
| --- | --- | --- |
| `type` | `"source"`, `"test"`, `"example"` or `"other"`. | Inferred from the name. |
| `paths` | Globs or directories. | `src/<name>/<language>`. |
| `resources` | Paths, or `new { from; into }` roots. | `src/<name>/resources`. |
| `dependsOn` | Source sets visible to this one. | Absent: test and example sets see `main`. Empty: nothing. |

See [Source Sets](https://elide.help/docs/toolchain/source-sets).

## Dependencies

```pkl
dependencies {
  maven {
    packages { "com.google.guava:guava:33.5.0-jre" }
    testPackages { "org.junit.jupiter:junit-jupiter:5.13.4" }
    substitutions { ["com.google.guava:guava"] = "libs/guava-patched.jar" }
  }
  npm { packages { "react@19.0.0" }; devPackages { "typescript@5.6.2" } }
  pypi { packages { "requests>=2" }; optionalPackages { ["dev"] { "pytest>=8" } } }
  spring { version = "4.0.0"; starter { "web" }; testStarter { "test" } }
}
```

| Block | Properties |
| --- | --- |
| `maven` | `packages`, `devPackages`, `modules`, `compileOnly`, `runtimeOnly`, `processors`, `testPackages`, `kotlinPlugins`, `exclusions`, `resolutions`, `substitutions`, `repositories`, `enableDefaultRepositories`, `localRepository` |
| `npm` | `packages`, `devPackages` |
| `pypi` | `packages`, `optionalPackages` (named groups); `elide install` does not install them. |
| `spring` | `version`, `starter`, `testStarter`; expands into `maven`. |

| Value | Form |
| --- | --- |
| Maven entry | `"group:artifact:version[:classifier]"`, a `MavenPackageSpec` (`group`, `name`, `version`, `classifier` or `coordinate`), a local JAR path, or a [project reference](#workspaces). |
| Repository | A URL, or a spec with `url`, `name`, `description`, `credentials` (`username`, `password`, `ntlmDomain`, `ntlmWorkstation`). |
| `resolutions` key | `group:artifact`; the value is an exact version overriding direct and transitive requests, without adding a dependency. See [version resolutions](https://elide.help/docs/toolchain/dependencies#maven-version-resolutions). |
| `substitutions` key | `group:artifact` (any version) or `group:artifact:version`; the value is a file path. |

A substitution keeps the coordinate's transitive dependencies and puts the file on every classpath in its place.
Paths resolve against the declaring manifest; members inherit the root's. Order the task that generates the file
first. Buckets and repositories: [Dependencies](https://elide.help/docs/toolchain/dependencies).

## JVM

```pkl
jvm {
  main = "example.Main"
  target = 25
  flags { "--add-opens=java.base/java.lang=ALL-UNNAMED" }
  defs { ["app.env"] = "dev" }
  java { release = 25; compiler { flags { "-Xlint:all" } } }
}
```

| Property | Meaning | Default |
| --- | --- | --- |
| `main` | Entrypoint class for `elide run`, JARs, applications and native images. | — |
| `target` | Bytecode target: `8` … `29`, `1.8`, `"latest"`, `"stable"` or `"auto"`. | `"auto"` (21) |
| `javaHome` | Java home for external compiles and forked JVMs. | Elide's own |
| `preferBali` | External JVM runs launch `bali` from `PATH` instead of `java`, or warn and use `java`. | `false` |
| `features.testing` | JUnit dependencies on test sets. | `true` |
| `features.automodules` | Automatic modules for JARs without a descriptor. | `true` |
| `features.graalvm` | `org.graalvm` SDK APIs on the compile classpath. | `false` |
| `java.source`, `java.release` | Source and `--release` levels; see [Java Compiler](https://elide.help/docs/toolchain/jvm/javac#build-projects). | `"auto"` |
| `java.compiler.mode` | `"embedded"` or `"external"`. | `"embedded"` |
| `java.compiler.flags` | Extra `javac` flags. | none |
| `java.sourceSets` | Per-source-set `release` and `flags`; see [Java Compiler](https://elide.help/docs/toolchain/jvm/javac#build-projects). | none |
| `java.bindings` | Named C-header binding groups for jextract; see below. | none |
| `java.sourceSets["<set>"].bindings` | Binding-group names compiled into that source set. | none |
| `flags` | JVM flags for `elide run`, the test JVM and start scripts. | none |
| `defs` | System properties (`-D`), applied wherever `flags` are. | none |
| `debug` | Compile with `-g`. | `true` |

### C header bindings

In 1.6.2, declare native bindings under `jvm.java.bindings` and select them for the Java source set that owns
their generated classes:

```pkl
jvm {
  java {
    bindings {
      ["api"] {
        headers { "native/api.h" }
        packageName = "example.nativeapi"
        headerClassName = "NativeApi"
        includeDirs { "native/include" }
        libraries { "answer" }
      }
    }
    sourceSets {
      ["main"] { bindings { "api" } }
    }
  }
}
```

| Binding property | Contract | Default |
| --- | --- | --- |
| `headers` | Header paths or globs; unmatched globs fail. | required |
| `packageName` | Java package for generated bindings. | required |
| `headerClassName` | Generated header class name. | required |
| `includeDirs` | Header search directories. | empty |
| `defines` | Preprocessor definitions, such as `API_VERSION=1`. | empty |
| `libraries` | Shared-library specifications used by generated lookup code. | empty |
| `useSystemLoadLibrary` | Use system library loading for the declared libraries. | `false` |

Paths resolve against the owning project. A group has one owning source set; dependent sets use its classes
through their normal dependencies. Declaring a group does not select it for compilation. These bindings require
a distribution with native tools. See [C header bindings](https://elide.help/docs/toolchain/jvm/javac#c-header-bindings) for
generation-only targets, exported sources, incremental inputs, and command-line selections, and
[jextract](https://elide.help/docs/toolchain/native/jextract) for native-library loading at runtime.

## Kotlin

```pkl
kotlin {
  apiLevel = "auto"
  languageLevel = "auto"
  compilerOptions { allWarningsAsErrors = true; optIn { "kotlin.time.ExperimentalTime" } }
  features { serialization = true; metro = false }
  toolchain = "embedded"
  plugins { ["org.jetbrains.kotlinx.serialization"] { ["mode"] = "json" } }
}
```

| Property | Meaning |
| --- | --- |
| `apiLevel`, `languageLevel` | `"auto"`, `"latest"`, `"stable"`, or `"2.2"` … `"2.5"`. |
| `compilerOptions` | `optIn`, `progressiveMode`, `extraWarnings`, `allWarningsAsErrors`, `suppressWarnings`, `verbose`, `freeCompilerArgs`, `apiVersion`, `languageVersion`, `includeRuntime`, `noStdlib`, `javaParameters`, `jvmTarget`, `noJdk`, `jvmTargetValidationMode`, `incremental` |
| `features` | `testing`, `kotlinx`, `defaultPlugins`, `serialization`, `coroutines`, `atomicfu`, `powerAssert`, `metro`, `compose`, `reflection`. `compose` defaults to `false` and requires an explicit Compose runtime dependency. |
| `toolchain` | `"embedded"`, `new Kotlin.ManagedKotlinToolchain { version }` or `new Kotlin.CustomKotlinToolchain { path }`. |
| `plugins` | Options per compiler-plugin id, passed as `-P plugin:<id>:<key>=<value>`. |

Defaults: [Kotlin Compiler](https://elide.help/docs/toolchain/jvm/kotlinc#build-projects).

## JavaScript

| Property | Meaning | Default |
| --- | --- | --- |
| `javascript.ecma` | Declared ECMAScript edition: a year `2015` … `2025`, `"stable"` or `"latest"`. `elide project info` shows it; it does not change which syntax runs or how code is built ([JavaScript language](https://elide.help/docs/javascript-language)). | `"stable"` |

## Toolchain

```pkl
toolchain { engines { ["elide"] = ">=1.6.0" } }
```

| Property | Meaning |
| --- | --- |
| `engines["elide"]` | Version or semver range, or `new { version = "…" }`; a running Elide outside it fails validation. |
| `engines[<other>]` | Accepted, not enforced. |

## Dev

```pkl
dev {
  source { platform = "github"; project = "example/app" }
  mcp {
    registerElide = true
    advice = true
    resources { new { path = "docs/ARCHITECTURE.md"; name = "architecture" } }
  }
}
```

| Property | Meaning | Default |
| --- | --- | --- |
| `source.platform`, `source.project`, `source.subpath` | Source host: `"github"`, `"gitlab"`, `"bitbucket"`, `"git"` or another name. | — |
| `mcp.registerElide` | Register Elide as an MCP tool. | `true` |
| `mcp.advice` | Expose `elide project advice` as an MCP resource. | `true` |
| `mcp.resources` | Extra MCP resources: `path`, `name`, `description`, `mimeType` (detected if omitted). | none |
| `server.host`, `server.port` | Unused by `elide dev`, which takes `--host`/`--port` (default `127.0.0.1:3000`). | `"0.0.0.0"`, `8080` |

## Web

| Property | Meaning | Default |
| --- | --- | --- |
| `web.css.minify` | Minify built CSS. | `true` |
| `web.css.targets` | Browser targets: `new { browser = "chrome"; version = "120" }`. | none |
| `web.minifyHtml` | Minify rendered HTML. | `true` |
| `web.browsers` | Browser targets for all outputs; `css.targets` overrides it for CSS. | none |

See [Web Toolchain](https://elide.help/docs/toolchain/web#manifest-settings).

## Artifacts

```pkl
import "elide:Jvm.pkl" as Jvm

artifacts {
  ["app"] = new Jvm.Jar {
    name = "app.jar"
    options { entrypoint = "example.Main" }
    sources { "main" }  // optional; empty packages every non-test source set
  }
}
```

The key names the build task. Outputs go under `.dev/artifacts/<kind>/`.

| Type | Module | Fields |
| --- | --- | --- |
| `Jar`, `SourceJar`, `JavadocJar`, `JvmApplication` | `elide:Jvm.pkl` | [JAR Assembly](https://elide.help/docs/toolchain/jvm/jar#project-artifacts) |
| `NativeImage` | `elide:NativeImage.pkl` | [Native Image](https://elide.help/docs/toolchain/jvm/native-image#project-artifact) |
| `WebImage` | `elide:NativeImage.pkl` | [WebImage](#webimage) |
| `ContainerImage` | `elide:Container.pkl` | [JVM Containers](https://elide.help/docs/toolchain/jvm/containers) |
| `StaticSite` | `elide:Web.pkl` | [Static Sites](https://elide.help/docs/toolchain/web/static-site) |

| Common field | Meaning |
| --- | --- |
| `dependsOn` | Artifact names to build first. |
| `name` (JARs) | `"app"` and `"app.jar"` both give `app.jar`; a `SourceJar` gives `app-sources.jar`. |

### WebImage

`WebImage` takes the [`NativeImage`](https://elide.help/docs/toolchain/jvm/native-image#project-artifact) fields `from`, `name`, `entrypoint`,
`moduleName`, `dependsOn` and `options`, has no `type`, and adds:

| Property | Values | Default |
| --- | --- | --- |
| `backend` | `"wasm-gc"` (host GC) or `"wasm-lm"` (linear memory). | `"wasm-gc"` |
| `runtime` | `"generic"`, `"browser"` or `"node"`. | `"generic"` |
| `autoRun` | Start the program when the loader loads. | `true` |
| `sourceMap` | Emit a JavaScript source map. | `false` |

`entrypoint` (or `jvm.main`) is required even with `autoRun = false`. Do not pass raw flags for these four settings.
Guide: [WebImage](https://elide.help/docs/toolchain/jvm/webimage#build-a-complete-project); flag-based artifacts:
[migration](https://elide.help/docs/toolchain/jvm/webimage#compile-without-a-project-manifest).

## Publishing

```pkl
publishing {
  maven {
    groupId = "dev.example"
    artifactId = "example"                 // default: the project name
    localRepository = "~/.m2/repository"   // the default
    artifacts { "lib"; "lib-sources" }      // default: every Jar, SourceJar and JavadocJar
    pom {
      name = "Example"
      description = "An example library."  // default: the project description
      url = "https://example.dev"
      licenses { new { name = "Apache-2.0"; url = "https://www.apache.org/licenses/LICENSE-2.0" } }
      developers { new { id = "jdoe"; name = "J. Doe" } }
      scm { url = "https://github.com/example/example" }
    }
    repositories {
      ["internal"] = "https://repo.example.dev/releases"  // or new Jvm.MavenRepositorySpec { url = … }
      ["central"] = new Publishing.CentralPortal {}       // Maven Central; import "elide:Publishing.pkl" as Publishing
    }
    signing { gpg { keyName = "0123ABCD" } }  // optional; omit keyName for gpg's default key
  }
}
```

`elide publish` builds the selected JARs and installs them with a POM into the local Maven repository. The version is
the project's `version`. A publication needs a `groupId`, a `version` and exactly one `Jar`. A `SourceJar` publishes
under its `classifier` (default `sources`), a `JavadocJar` under `javadoc`. A relative `localRepository` resolves
against the manifest that declares it.

| Manifest | Published POM |
| --- | --- |
| `packages`, `modules` | `compile` dependencies |
| `runtimeOnly` | `runtime` dependencies |
| `compileOnly`, `devPackages`, `processors`, `kotlinPlugins`, `testPackages` | Omitted |
| Versionless `exclusions` | An `<exclusion>` on every dependency |
| A sibling project | The sibling's own publishing coordinates |
| A `Jar.from` sibling | Its `packages`, `modules` and `runtimeOnly`, unless already listed |
| A local JAR path | An error; consumers cannot resolve it |

The POM also lists libraries Elide puts on a Kotlin project's classpath without a declaration, as `compile`
dependencies, because consumers need them at run time: `kotlin-stdlib` and `kotlin-reflect` at the project's Kotlin
version, and the runtimes `kotlin.features` turns on (`kotlinx-coroutines-core`, `kotlinx-serialization`, the Metro
runtime). These appear in the published POM even though `elide.pkl` doesn't name them. Turn one off where it comes
from (`kotlin.features`, or `kotlin.compilerOptions.noStdlib`), or declare it yourself to pin a different version.
`jvm.features.graalvm` adds the GraalVM SDK at `runtime` scope.

`elide publish --to <name>` publishes to a repository from `repositories` instead, with `.md5`, `.sha1`, `.sha256` and
`.sha512` checksums for every file. Credentials come from `ELIDE_MAVEN_<NAME>_USERNAME` and
`ELIDE_MAVEN_<NAME>_PASSWORD`, where `<NAME>` is the repository name uppercased with every character other than a
letter or digit turned into `_` (`internal` → `ELIDE_MAVEN_INTERNAL_USERNAME`, `my-repo` → `ELIDE_MAVEN_MY_REPO_…`),
and otherwise from the repository's `credentials`. Two names that map to the same variables (`my-repo` and `my_repo`)
are refused. Plain `http://` repositories other
than localhost are refused without `--allow-http-repositories`.

With `signing` set, a remote publish signs every file with the `gpg` binary (`<file>.asc`); `signing {}` signs with
`gpg`'s default key, and `signing { gpg { keyName = "…" } }` picks one. A local publish signs only with
`--sign`, and `--no-sign` turns signing off. `gpg-agent` asks for the passphrase. For CI, set `ELIDE_GPG_PASSPHRASE` and
Elide passes it to `gpg` through loopback pinentry. Elide never reads the key itself.

A `CentralPortal` repository publishes to Maven Central through the Central Publisher Portal. Create a user token in the
Portal and pass it as `ELIDE_MAVEN_<NAME>_USERNAME` and `ELIDE_MAVEN_<NAME>_PASSWORD`. Every project of the run goes
into one signed, checksummed bundle and one deployment, which the Portal validates and releases as a unit. Elide waits
until the Portal has validated it, and fails with the Portal's errors if it does not pass. `--no-wait` returns right
after the upload instead.

| `CentralPortal` property | Meaning | Default |
| --- | --- | --- |
| `publishingType` | `USER_MANAGED`: release the validated deployment in the Portal yourself. `AUTOMATIC`: release once validated. | `USER_MANAGED` |
| `url` | Portal to upload to. | `https://central.sonatype.com` |

Before building, Elide checks what Central requires and reports everything missing at once: `pom` `name`,
`description`, `url`, `licenses`, `developers` and `scm.url`; a `SourceJar` and a `JavadocJar` artifact; signing (so
`--no-sign` is refused); and a version that is not a `-SNAPSHOT`.

In a workspace, members inherit the root's `publishing` block except `artifactId` and `artifacts`, and a member's own
settings win field by field. Repositories are inherited by name. A project that builds no JAR publishes nothing it
inherits.

To publish only some of a workspace's projects, name them (`elide publish core util`) rather than changing their
`publishing` blocks, or pass `--skip-existing` to leave out every version already in the target; see
[`elide publish`](https://elide.help/docs/cli-references#publish).

## Testing

```pkl
import "elide:Testing.pkl" as Testing

testing { reports { Testing.xmlReport() } }
```

| Property | Meaning |
| --- | --- |
| `testing.reports` | Report formats for every runner, written to `.dev/reports/tests/<language>/<source-set>/`; same as `--reporter=junit`. |

## Coverage

```pkl
import "elide:Coverage.pkl" as Coverage

coverage {
  enabled = true
  scope = "tests"
  jvm {
    excludes { "org.example.generated.*" }
    reports { Coverage.xmlReport(); Coverage.htmlReport() }
  }
  js { paths { "src" } }
}
```

| Property | Meaning | Default |
| --- | --- | --- |
| `coverage.enabled` | Collect coverage without `--coverage`. | `false` |
| `coverage.scope` | Tasks that collect: `"tests"`, `"runs"` or `"all"`. `--coverage` always covers the task it is passed to. | `"tests"` |
| `coverage.jvm.reports` | JVM report formats: `Coverage.htmlReport()`, `xmlReport()`, `csvReport()`, written to `.dev/reports/coverage/jvm/<task>/`. | none |
| `coverage.jvm.excludes` | Class-name patterns (`*`, `?`) left uninstrumented, such as generated code. | none |
| `coverage.js.paths` | JavaScript and TypeScript source roots to report, as LCOV in `.dev/reports/coverage/js/lcov.info`. Empty reports the whole project. | none |

See [Testing › Coverage](https://elide.help/docs/toolchain/testing#coverage).

## Workspaces

```pkl
workspace { members { "core"; "app" } }
```

Each member is a directory with its own `elide.pkl`; members cannot have members. `elide init --template workspace`
creates a root with a `core` library and an `app` that depends on it.

| Behavior | Rule |
| --- | --- |
| Build scope | From the root, `elide build`/`elide test` cover every project; from a member, only that member. |
| Cross-project tasks | `elide build core:compile-kotlin-main`, or `elide build core` for its default tasks. |
| Shared state | Resolved dependencies and build state live in the root `.dev`; outputs stay in each project's `.dev`. |
| Version pins | A member can omit a Maven version and take the root's; a conflicting version is an error. |

```pkl
// core/elide.pkl
import "elide:Jvm.pkl" as Jvm

dependencies {
  maven {
    packages { "com.google.guava:guava" }  // version from the root
  }
}

artifacts {
  ["jar"] = new Jvm.Jar { name = "core" }
}
```

```pkl
// app/elide.pkl
local core = project("core")

dependencies { maven { packages { core } } }
```

| Reference | Meaning |
| --- | --- |
| `project("core")` | The project's `Jvm.Jar` artifact and its compile classpath, built when needed. JVM sources alone do not count. |
| `project("core").artifact("fat")` | Pick one JAR when the project declares several. |
| `project("core").friend()` | Also see `core`'s Kotlin `internal` declarations, not its dependencies'. Only in `packages`, `modules`, `compileOnly`, `testPackages`. |
| `new Jvm.Jar { from { core } }` | Unpack `core`'s JAR into this one, building it first; no dependency needed. |
| `module.project("core")` | Use in place, inside an object body; bare `project(…)` does not resolve there. |
| `new Artifacts.ProjectArtifact { project = "core"; artifact = "fat" }` | Explicit form; `import "elide:Artifacts.pkl" as Artifacts`. |

A reference to a project with no JAR, or a misplaced `friend()`, fails when the build is configured.

## Micronaut

See the [Micronaut guide](https://elide.help/docs/toolchain/jvm/micronaut) for complete build, test, and Native Image workflows.

Use the **Micronaut Core version**, not the platform BOM version. Core module names expand to
`io.micronaut:micronaut-<name>:<version>`; the matching `micronaut-inject-java` processor is installed
and used by Java compilation. Independently versioned modules, such as Micronaut Serialization,
remain ordinary Maven coordinates.

```pkl
dependencies {
  micronaut {
    version = "4.10.23"
    modules { "runtime"; "http-server-netty" }
    testModules { "http-client" }
  }
}
jvm {
  micronaut {
    config {
      micronaut {
        application { name = "example" }
        server { port = 8080 }
      }
      custom {
        enabled = true
        origins = new Listing { "https://example.com" }
      }
    }
  }
}
```

The optional `jvm.micronaut.config` accepts arbitrary JSON-compatible keys and values. Elide renders
it with Pkl's JSON renderer and stages `application.json` on production classpaths and in JARs.
Elide adds the matching `micronaut-jackson-core` runtime dependency for the JSON configuration loader.
When supplying your own JSON resources, declare that loader yourself.
An authored `application.json` conflicts with this generated resource and fails the build; omit the
block to supply your own configuration through resources. Environment-specific files may still be
provided through resources. Changes to generated configuration invalidate resource and JAR tasks.

Direct `io.micronaut:micronaut-*` Core dependencies also select the matching processor after workspace
version pins are applied. Conflicting Core or processor versions fail validation. Satellite-module
versions and transitive-only framework dependencies do not select a processor: declare a Core module
or use the shorthand. This integration uses the existing Java annotation-processing path; it does not
enable KSP or Kotlin-source annotation processing in project builds.

Runnable examples are in
[`tools/smoketests/micronaut-pkl`](https://github.com/elide-dev/elide/tree/main/tools/smoketests/micronaut-pkl)
and
[`tools/smoketests/micronaut-resources`](https://github.com/elide-dev/elide/tree/main/tools/smoketests/micronaut-resources).
The [sample guide](https://github.com/elide-dev/elide/blob/main/docs/micronaut-samples.md) includes run,
packaging, and smoke-test commands.

## Test containers

`testing.testcontainers` describes disposable infrastructure shared by the project's selected test runners.
Declare named entries in `containers`, each with an `image`, optional `command` and `environment`, exposed
TCP `ports`, and a `wait` strategy. `environment` on the outer block binds container connection values
into JavaScript/TypeScript and JVM test environments. `startupTimeoutSeconds` bounds total startup.

See [Shared test containers](https://elide.help/docs/toolchain/testing#shared-test-containers) for the complete configuration,
readiness options, prerequisites, and lifecycle.
