Skip to content

Releases: BuildMosaic/Mosaic

Mosaic 0.7.0

Choose a tag to compare

@github-actions github-actions released this 06 Oct 03:37
08aa4fe

Mosaic 0.7.0 release notes

Mosaic 0.7.0 gives each request an explicit execution lifetime, distinguishes
borrowed Canvas values from owned resources, and isolates keyed failures. This
guide covers upgrading from 0.6.0; see compatibility before
updating runtime dependencies and optional analysis tooling.

Scoped request execution

Use Canvas.withMosaic as the canonical request entry point. Keep application
services in a long-lived Canvas, bind request input in a child Canvas, and run the
complete handler inside one scoped block:

import org.buildmosaic.core.injection.withMosaic

// In a suspending request handler; RequestKey and ResponseTile are your declarations.
applicationCanvas.withLayer {
  instance(RequestKey, value)
}.withMosaic {
  compose(ResponseTile)
}

Tile producer work inherits the calling coroutine's dispatcher and context and
is scoped to that request. Producers are supervised: cancelling a consumer's
coroutine stops that consumer's wait without automatically cancelling shared
producer work. Cancelling the request cancels its unfinished producers. Leaving
withMosaic, normally or exceptionally, cancels unfinished speculative work and
waits for producer and attached-child cleanup.

Canvas.create() remains available but is deprecated at WARNING level.
Migration normally means moving the complete handler invocation into
requestCanvas.withMosaic { handler(this) }, rather than returning or retaining
a Mosaic between requests. Each block creates a fresh Mosaic and cache.

withMosaic does not close its Canvas. If the request child constructs owned
AutoCloseable resources, surround composition with use as shown in the
resource guide.

Canvas instance bindings

single { ... } constructs a Canvas-owned value. Canvas closes successfully
constructed local AutoCloseable values on close or construction rollback.
instance(existing) registers an already-existing, externally owned value:
Canvas must never close it, including when construction fails or is cancelled.
The external owner remains responsible for its lifetime.

The three forms mirror single. Each pair below shows alternatives for the same
binding: choose the owned constructor or the borrowed instance, not both.

single<Client> { Client() }
instance(existingClient)

single<Client>("primary") { Client() }
instance<Client>("primary", existingClient)

single(ClientKey) { Client() }
instance(ClientKey, existingClient)

Use instance<Service>(existingService) when binding an interface or supertype;
instance("primary", existingClient) infers the type when that is the intended
binding. Positional CanvasKey bindings also work for String values, such as
instance(OrderKey, orderId). Each key can be registered only once per layer.
Instance bindings are available to constructors through paint and use the same
typed-key, qualifier, local-first lookup, and parent-fallback rules.
A child does not close parent-owned resources. Replace single { existingService }
with instance(existingService) when the application or framework owns the service.

MultiTile outcomes

Within one Mosaic, the same MultiTile retains one terminal outcome per equal
key
, including failures. Repeating a composition is not a retry.

  • A present map entry is successful even when its value is null and V is nullable.
  • A requested key omitted from the returned map fails that key with
    NoSuchElementException; present sibling values remain retained.
  • A bulk provider invocation failure fails unfinished keys in that physical
    invocation, preserving outcomes already published.
  • perKeyTile failures are isolated to that key.
  • chunkedMultiTile failures are isolated to that physical chunk's unfinished keys.
  • Successful sibling keys remain cached and are reused by later callers.

compose(tile, keys) is strict: it throws if a requested key fails, rather than
returning a partial map. composeAsync(tile, keys) returns Map<K, Deferred<V>>,
so callers can await and handle each key separately. Pending uncached keys can
coalesce before execution; exact physical batch boundaries are not guaranteed.
Request cancellation still cancels unfinished work across keys. See
MultiTile for the current contract.

Deferred ownership

Every composeAsync overload returns Mosaic-owned shared Deferreds. Callers
may await them freely. If a consumer no longer needs a result, cancel that
consumer's coroutine or wait. Directly cancelling a returned shared Deferred can
affect other consumers waiting for the same Tile or key. No wrapper or new result
abstraction is required.

Optional analysis

Analysis recognizes supported withMosaic execution and instance bindings.
Proven synchronous Tile result cycles produce
MOSAIC_CYCLIC_TILE_DEPENDENCY: a hard correctness error that cannot be configured
or suppressed. MOSAIC_RECURSIVE_TILE and MOSAIC_RECURSIVE_MULTITILE both default
to ERROR, but can be configured to ERROR, WARNING, or OFF.
Kotlin @Suppress is available for supported intentional local recursion.

Deferred-await linkage and MultiTile key equality/termination reasoning remain
outside the proof boundary. A configurable recursive finding is not proof of a
same-Mosaic cyclic result dependency. Use the canonical
analysis configuration reference
for rule definitions, suppression scopes, enforcement, and project restrictions.

Metadata changes from format 3 / analysis-contract-2 to format 4 /
analysis-contract-3. Old dependency summaries are incompatible and must be
regenerated with matching 0.7 analysis tooling. The resource path
META-INF/mosaic-analysis/v1/summary.json is unchanged; compatibility comes from
its header. Keep the Gradle and compiler plugins at the same Mosaic version.

Updated examples

The Spring, Ktor, and Micronaut examples demonstrate delegated,
named Tiles, Canvas-supplied application services, instance(...), child request
Canvases, and one withMosaic lifetime per request. Ktor uses coroutine routes;
Spring and Micronaut use suspending controllers. Synchronous application setup
uses runBlocking only to construct the application Canvas.

Runtime cost

The six changed scoped-drain and fully cached MultiTile fixtures have fresh
0.7.0 baselines, commands, and measurement boundaries.
Scoped diamond/sibling requests measured 2.743/4.036 µs/op; fully cached reads
measured 0.042–10.912 µs/op for 1–512 keys. Allocation was measured separately.
These fixtures are not directly comparable with their old versions; historical
0.5/0.6 tables and application measurements are unchanged.

Compatibility

  • Runtime source/API: withMosaic and instance are additions;
    Canvas.create() is a WARNING deprecation, not a removal. Existing public
    runtime method signatures remain available. Builds treating warnings as errors
    may need to migrate deprecated calls.
  • JVM binary surface: the inspected Kotlin-public core API descriptors are retained.
    The internal MultiTile provider representation changed, including its mangled
    JVM getter; code calling internal members from Java or reflection can break.
    This is not a general binary compatibility guarantee for 0.x. Recompile and
    test consumers when upgrading, especially code using inlined DSLs.
  • Runtime behavior: scoped execution changes cancellation and cleanup;
    MultiTile now retains per-key outcomes and successful siblings across provider
    failures, and treats present nullable values as success. Review recovery,
    cancellation, and resource ownership in your application.
  • Runtime requirements: Kotlin/JVM, Java 17 or later, and Kotlin consumer
    compiler 2.3.0 or later remain the documented boundary. Dependencies remain
    stdlib/kotlin-test 2.4.20, coroutines core/test 1.11.0, and OpenTelemetry API 1.66.0.
  • Optional analysis: format/semantic metadata is incompatible with 0.6
    summaries. Only Kotlin compiler and Gradle plugin 2.4.20 are supported,
    within the documented pure main Kotlin/JVM boundary. This restriction does
    not apply to runtime-only consumers.

For upgrades from earlier releases, also read the
0.6.0 Canvas migration. The runtime BOM
continues to align core, test, and OpenTelemetry; analysis tooling remains outside
it. See runtime and analysis requirements.

[0.7.0] - 2026-10-05

Added

  • Canvas.withMosaic for one request lifetime: producer work inherits the calling
    coroutine; every exit cancels unfinished work and waits for cleanup.
  • Typed, qualified, and CanvasKey instance(...) bindings for externally owned
    values that Canvas never closes, including on construction rollback. Qualifiers
    precede values, mirroring single<T>("primary") { ... }.
  • Analysis recognition of supported withMosaic and instance usage, proven
    Tile-cycle detection, and configurable/suppressible recursive Tile/MultiTile
    policy with ERROR/WARNING/OFF severities. Proven cycles cannot be disabled.

Changed

  • MultiTile retains one terminal outcome per equal key. Per-key and chunk
    failures preserve successful siblings; strict bulk compose still throws on
    any requested failure. Returned Deferreds remain Mosaic-owned shared work.
  • Analysis metadata moves to format 4 / analysis-contract-3; dependency
    summaries must be rebuilt with matching 0.7 tooling. Optional analysis still
    supports exactly Kotlin compiler/Gradle plugin 2.4.20.
  • Spring, Ktor, and Micronaut examples use named T...
Read more

Mosaic 0.6.0

Choose a tag to compare

@github-actions github-actions released this 03 Oct 22:59
b193662

Mosaic 0.6.0 release notes

Mosaic 0.6.0 brings named Tiles and execution tracing to a shared runtime with
Mosaic-owned Canvas construction and resource ownership.

Canvas and execution

Canvas is now a final Mosaic-owned class, created through canvas and
withLayer. It holds resolved bindings and inherited runtime configuration;
each Mosaic owns its request cache, batching, and execution state. mosaic-test
uses that same runtime with substituted Tiles and a test dispatcher.

Ordinary request layers that bind values stay small:

applicationCanvas.withLayer {
  single(OrderKey) { orderId }
}.create().compose(OrderPageTile)

Scope or close a Canvas when its local bindings own AutoCloseable resources.
Closing a child leaves parent resources open. Construction shares concurrent
binding resolution, detects dependency cycles, and cleans up successfully created
local resources on failure or cancellation in reverse creation order.

Named Tiles and observation

Use val OrderTile by singleTile { ... } to capture a property name. The same
syntax works with multiTile, perKeyTile, and chunkedMultiTile. Aliases retain
the first bound name and the original Tile instance; names do not change caching.
Ordinary = declarations remain valid and initially have no name.

The new mosaic-opentelemetry module adds
spans to your application's OpenTelemetry pipeline:

val applicationCanvas = canvas {
  tracing { openTelemetry }
}

Use tracing() with an already registered global provider, including a Java agent.
Spans use delegated Tile names when available, otherwise Mosaic single or
Mosaic multi. Shared work and additional batch callers appear as links; cache
hits create no spans. Context survives suspension and dispatcher changes.
Execution spans include attached child coroutines, even when a result is available
earlier. Observation failures are isolated from Tile results; each Mosaic retains
ownership of its shared work, caching, and batching.
Your application owns the SDK, sampling, exporters, and shutdown. Automatic
telemetry excludes keys, values, request identifiers, and exception contents;
delegated property names are visible as span names.

The runtime BOM now aligns mosaic-core, mosaic-test, and the optional adapter.
Adding the BOM alone does not install tracing.

Compatibility and migration

  • Canvas source and binary break: external implementations, inheritance, and
    delegation of the former Canvas interface are unsupported. MosaicCanvas is
    removed. Replace direct implementation/construction with canvas or withLayer,
    and recompile consumers against 0.6.0. CanvasBuilder/CanvasFactory construction
    belongs to Mosaic; use the public DSL.
  • Kotlin: built with compiler and Gradle plugin 2.4.20, language/API level
    2.4, stdlib 2.4.20, and coroutines 1.11.0. Runtime artifacts are tested
    with Kotlin 2.3.0 consumers. Java 17 remains the runtime minimum; repository
    builds and framework examples use JDK 21 and Gradle 8.14.4.
  • Analysis: requires exactly Kotlin compiler and Gradle plugin 2.4.20.
    Rebuild dependency summaries generated with other compiler versions. Supported
    Mosaic-owned delegated declarations preserve contracts; arbitrary delegates and
    member-dependent Tile properties remain conservative analysis boundaries.
  • Delegate operators: Mosaic's member provideDelegate/getValue operators
    take precedence over custom extension operators when consumers recompile.
  • OpenTelemetry: the adapter exposes API 1.66.0 and includes core. It does
    not install an SDK or the optional Kotlin context extension in your application.

Runtime cost

A targeted JMH comparison with released 0.5.0, with tracing disabled, measured
the following synthetic runtime operations:

Operation 0.5.0 µs/op 0.6.0 µs/op
Four-branch shared diamond 5.6 5.7
Four sibling coalescing consumers 7.8 8.0
64-Tile chain 27.7–28.4 28.9

Across comparison orders, the remaining timing differences were about
0.09–0.15 µs/op for diamond, 0.16 µs/op for sibling coalescing, and
0.6–1.2 µs/op for the 64-Tile chain. The diamond and coalescing fixtures
allocated about 1.2 KB/op and 1.6–1.9 KB/op more, respectively.
Cold trivial Tiles and cold 16-key batches had overlapping timing intervals.
Cost depends on graph shape; the technical guide includes width, depth, cache,
and complete execution-drain measurements.

These are elapsed microbenchmark timings, not application CPU/request or HTTP
latency. Timing and allocation were measured separately on JDK 21; reversed
comparison order confirmed the graph differences. See the
comparison methodology and results.
The application performance results describe their
documented source revision and were not remeasured for 0.6.0.

See the core guide,
analysis setup, and
changelog for usage and the complete release summary.

[0.6.0] - 2026-10-03

Added

  • Delegated Tile naming with val OrderTile by singleTile { ... } and equivalent
    MultiTile factories. Aliases preserve the first name and the same cache identity.
  • Supported execution observation for actual Tile executions and MultiTile batches,
    including caller context, shared-work relationships, and sanitized completion.
    Observation failures are isolated from Tile results.
  • Optional mosaic-opentelemetry module and Canvas tracing() / tracing { openTelemetry }
    DSL. Spans use delegated Tile names, propagate coroutine context, and link shared work.
    The runtime BOM aligns the adapter alongside core and test libraries.

Changed

  • Canvas is a final Mosaic-owned class built with canvas or withLayer.
    External Canvas implementations and MosaicCanvas are removed; migrate to the DSL.
  • Canvas construction shares concurrent dependency resolution, detects cycles, and
    cleans up local resources on failure/cancellation in reverse creation order.
  • Kotlin compiler and Gradle plugin move to 2.4.20 with language/API level 2.4;
    runtime dependencies use stdlib 2.4.20 and coroutines 1.11.0. Runtime consumers
    require Kotlin 2.3.0 or later; JVM 17 remains the runtime target.
  • Optional analysis supports exactly Kotlin compiler/Gradle plugin 2.4.20 and
    recognizes supported Mosaic-owned delegated Tile declarations. Older compiler
    summaries must be regenerated.

Mosaic 0.5.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 04:30
4ff8a53

Mosaic 0.5.0 release notes

Mosaic 0.5.0 adds opportunistic MultiTile coalescing. Independently discovered,
uncached keys for the same MultiTile can combine before scheduled execution
begins, without caller coordination or an intentional batching delay. Exact
batch boundaries depend on scheduling.

See the MultiTile guide for batching
strategies and the performance results for the
newly published measurements.

[0.5.0] - 2026-09-26

Changed

  • MultiTile opportunistically combines newly pending uncached keys for the same
    MultiTile within a request Mosaic before scheduled execution begins. Ready work
    is not intentionally delayed; exact batch boundaries depend on scheduling.

Mosaic 0.4.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 01:53
e237f1d

Mosaic 0.4.0 release notes

Mosaic 0.4.0 makes application composition visible and removes most analysis-root configuration.

Run mosaicGraph to generate Markdown and Mermaid dependency documentation. The graph shows Tiles and MultiTiles, Canvas dependencies, selected dependency relationships, and findings and uncertainty for each application root. It is static documentation, not a runtime execution trace.

In APPLICATION mode, an empty roots configuration now discovers safe outermost Mosaic execution boundaries across selected local and dependency contracts, including receiver-specialized template and framework entry methods. Existing explicit roots remain an exact override and a fallback when discovery cannot establish a safe boundary. Unsafe discovery fails rather than silently claiming verification. Applications already configuring roots can keep doing so; omitting them opts into automatic discovery.

Analysis remains optional and currently supports Kotlin/JVM 2.2.10 only. Runtime use does not require the analysis plugin. See the analysis plugin guide for details.

[0.4.0] - 2026-09-24

Added

  • Automatic discovery of safe application analysis roots from selected contracts,
    including local and dependency template/framework paths specialized for
    application receivers.
  • mosaicGraph generates Markdown and Mermaid diagrams for local contracts,
    relevant dependencies, and application root findings.

Changed

  • Application roots are optional; explicitly configured roots replace automatic
    selection exactly.

Mosaic 0.3.0

Choose a tag to compare

@github-actions github-actions released this 24 Sep 09:40
0b3cc46

Mosaic 0.3.0 release notes

The 0.3.0 changelog entry is the source for the user-facing
change list and the future GitHub Release text. Its release date is
2026-09-23.

Mosaic 0.3.0 brings optional static Canvas contract analysis to the existing
runtime. Applications can verify configured roots; libraries can export
contracts for consumers. Users choose STANDARD or STRICT enforcement.
Analysis runs with normal Kotlin compilation and requires no second production
compiler invocation.

For installation, see the runtime quick start and
analysis plugin guide. The analysis
plugin supports Kotlin/JVM 2.2.10 only within its documented pure main
source boundary. The runtime works without the analysis plugin or a Mosaic KSP
processor.

[0.3.0] - 2026-09-23

Added

  • Optional static Canvas contract analysis for supported Kotlin/JVM applications
    through the org.buildmosaic.analysis Gradle plugin.
  • APPLICATION analysis roots and LIBRARY contract export. Published library
    contracts can be checked when an application consumes them.
  • STANDARD enforcement, which warns on unverifiable paths, and STRICT
    enforcement, which also fails on those paths. Both fail proven missing Canvas
    requirements.
  • Same-version analysis support and compiler artifacts for the Gradle plugin to
    resolve automatically.

Changed

  • Analysis extracts contracts incrementally during normal Kotlin compilation;
    production builds do not launch a second Mosaic compiler process.
  • Mosaic runtime use no longer requires the old Mosaic registration plugins or
    KSP processors. The runtime BOM continues to align mosaic-core and
    mosaic-test.
  • Low-level dependency-provider and analysis-extractor implementation types are
    no longer part of the Kotlin source API.

Fixed

  • Child Canvas construction can resolve bindings from its parent Canvas.
  • Concurrent tile composition shares cached and in-flight results safely.
  • Published runtime and test dependencies expose the coroutine libraries needed
    to compile against their public APIs.

Analysis limits

Analysis is optional and currently supports Kotlin/JVM 2.2.10 only within
the documented pure main project boundary.