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.
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).
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
ServiceLoaderproviders 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 syntheticprovidesdirectives in the module descriptor. Applications never declare generated providers (_VaubanComponents,$$CassiniAdapter…) inmodule-info.java. -
Hot reload —
vidocq:devsignals 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
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 |
|---|---|
|
Lifecycle interface with |
|
Handed to |
|
String-property view passed to |
|
Typed configuration API aligned with MicroProfile Config concepts — |
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.listindex consumed by Vauban to boot without classpath scanning or reflection; -
for each
vidocq-runtime-<x>-extensionthe application depends on, a matchingvidocq-runtime-<x>-extension-codegenbundle 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 withoutThreadLocal. -
Health checks — executed on the virtual thread of the incoming
/healthrequest; no dedicated scheduler thread.
No platform thread pool is created by default. The rule is documented in the workspace root CLAUDE.md.
Recommended reading
-
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
-
Concepts — formal vocabulary
-
TCK status — conformance verification
-
vidocq-runtime-core — boot, lifecycle