This page describes what happens between mvn package and the first 200 OK. It follows the code through vidocq-runtime-maven-plugin, vidocq-runtime-core and the SPI, and details the extension grammar.

Overview — from source to runtime

Diagram

Class loading — the Vauban application layer

Vidocq does not let the application own its class loading: whatever the launch vehicle (IDE right-click on a @VidocqMain trampoline, packaged distribution scripts, vidocq:dev, the CLI), the application resolves into a child ModuleLayer whose defining loader is Vauban’s VaubanClassLoader — the runtime and its extensions stay in the boot layer, exports/opens keep being enforced, and every application class flows through the loader’s transformer chain at definition (client-proxy weaving by the cdi-proxifier, sjar decryption by the source plugins).

Diagram

Key mechanics (implemented in vauban-classloader, consumed by VidocqAppLayer/Vidocq.run):

  • Boot-layer shadowing — the child layer resolves the SAME module names the IDE put on the module path; the layer loader is self-first for its archives, so every internal reference of a layer class resolves against the woven world, never against its un-woven boot-layer twin. Duplicate ServiceLoader providers are deduplicated by class name, preferring the layer’s copy.

  • Services promotion — when the layer resolves an application module, the archive’s META-INF/services/* files are promoted to synthetic provides directives in the module descriptor. Applications never declare generated providers (_VaubanComponents, $$CassiniAdapter…) in module-info.java.

  • Hot reload — vidocq:dev signals the running JVM after a recompile; the runtime shuts the deployment down, discards the layer, resolves a fresh one over the new classes and boots again (same process, warm JIT, debugger attached). Cassini’s static discovery caches are reset between deployments (io.vidocq.cassini.runtime.CassiniMaintenance).

  • Compatibility net — a legacy main that boots the runtime without the layer still works: unwoven beans are handled by the Vauban load-time weaving agent (dynamic attach, with the JDK’s warning) and the log suggests the trampoline.

The full engine documentation (source/transformer SPI, weaving tiers, diagnostics options) lives in the Vauban documentation, Internals → Class loading.

Boot sequence diagram

Diagram

The lifecycle itself is sequential on a single thread — extensions run one after another in priority order, which keeps boot diagnosis trivial. On shutdown, onStop() walks the same list in reverse order. Concurrency lives inside the bricks: once the listeners are open, Chappe serves every connection on its own virtual thread.

Extension mechanism

Public SPI

The vidocq-runtime-spi module exposes the contribution interfaces. The main types shipped today:

Type Role

io.vidocq.runtime.spi.VidocqExtension

Lifecycle interface with name(), priority(), configure(VidocqConfiguration), beforeStart(VaubanContainerBuilder), onStart(ExtensionContext), onStop() and configKeys(). Registered via provides … with … in module-info.java (or META-INF/services), loaded through ServiceLoader.

io.vidocq.runtime.spi.ExtensionContext

Handed to onStart — access to the Vauban container(), the CDI beanManager(), the legacy configuration() and the typed config().

io.vidocq.runtime.spi.VidocqConfiguration

String-property view passed to configure: property(key) returns Optional<String>, plus property(key, default) and portFor(extensionName, defaultPort) helpers.

io.vidocq.runtime.spi.config.VidocqConfig

Typed configuration API aligned with MicroProfile Config concepts — getValue(key, type) returns Optional<T>; pluggable ConfigSource, ConfigSourceProvider and Converter<T>.

Source: vidocq-runtime-spi/src/main/java/io/vidocq/runtime/spi/.

Compile-time indexing (APT)

The vidocq-runtime-core-codegen POM bundle wires the Vauban indexer (vauban-processor) into every application build. At compile time it scans CDI-annotated classes and produces:

  • the META-INF/vauban-beans.list index consumed by Vauban to boot without classpath scanning or reflection;

  • for each vidocq-runtime-<x>-extension the application depends on, a matching vidocq-runtime-<x>-extension-codegen bundle adds the extension’s own APT output (e.g. Cassini’s statically generated dispatch).

Extension discovery itself needs no index file: it is plain ServiceLoader over the provides io.vidocq.runtime.spi.VidocqExtension with … directives of the modules on the module path.

Static generation, no recording

There is no bytecode-recording machinery in vidocq-runtime-core: nothing is "replayed" at startup. Everything that would otherwise require runtime reflection is generated at compile time by the bricks themselves — Vauban emits client proxies and interceptor plumbing via the Class-File API (JEP 484), Cassini generates its REST dispatch statically — and the runtime core merely runs the extension lifecycle over those pre-built artifacts.

Threading model

  • Bootstrap — a single platform thread, no pool. Boot is intentionally sequential to ease diagnosis.

  • HTTP I/O — Executors.newVirtualThreadPerTaskExecutor() on the Chappe side. One connection = one virtual thread. No reactor, no callbacks.

  • Persistence — JDBC pool from Mansart with non-fair Semaphore. ScopedValue<Connection> propagates the current connection inside a transaction without ThreadLocal.

  • Health checks — executed on the virtual thread of the incoming /health request; no dedicated scheduler thread.

No platform thread pool is created by default. The rule is documented in the workspace root CLAUDE.md.

  • Source: vidocq-runtime-core/src/main/java/io/vidocq/runtime/core/ (boot orchestrator)

  • SPI: vidocq-runtime-spi/src/main/java/io/vidocq/runtime/spi/

  • Maven plugin: vidocq-runtime-maven-plugin/

  • Examples: vidocq-runtime-examples/vidocq-runtime-cassini-rest-example/, vidocq-runtime-mansart-h2-example/

Next steps