Issue #370 was filed back when the simulation framework still required Java for things that should have been YAML-declarable: key extractors, exhaustion policies, thresholds, corpus loading. By the time I got to it, the #352 simulation DX work had already landed most of that — YamlSimulationConfig parses key-extractor, exhaustion-policy, and threshold natively, and corpus loading (inline and external files) works out of the box. Three of the four gaps were already closed.

Two real gaps remained, and they’re the interesting ones.

Parameter names as key extractor specs

The DeclarativeExtractorFactory already supported identity, field:accountId, and composite:x,y. But if you had a method like balance(String accountId), the natural YAML declaration is key-extractor: accountId — just the parameter name. That didn’t work. You had to write key-extractor: identity or key-extractor: field:accountId depending on whether the input was a primitive or a record.

The fix has two pieces. The APT (SimulationDecoratorProcessor) already knew the parameter names at build time via method.parameterName(i) — it just wasn’t doing anything with them. We added a generateParameterEntries() method that emits a META-INF/simulation-parameters.properties file alongside the decorator and QN constants. Format is simple: spi.method=paramName:0,paramName:1.

At runtime, a new ParameterRegistry loads these properties files from the classpath (multiple JARs aggregate via ClassLoader.getResources()). The factory’s new create(spec, qualifiedName) overload tries the existing prefix-based specs first (identity, field:*, composite:*, rest-client), then falls through to the registry. If the spec matches a parameter name, it creates the right extractor automatically — identity for single-arg methods, positional array extraction for multi-arg.

The error message when a bare name doesn’t match is deliberate: it lists the valid parameter names from the registry, so the YAML author gets immediate feedback instead of a generic “unknown spec” message.

Format-independent corpus loading

The original YamlSimulationConfig had all its corpus file loading buried in private methods — loadExternalCorpusFiles() and openStream(). That logic assumed YAML format, and it wasn’t accessible to external consumers like the pages repo’s ScenarioOrchestrator, which was importing a YamlCorpusLoader class that didn’t exist (it had been absorbed into YamlSimulationConfig during the #361 unified YAML work).

The feedback that shaped this design: corpus data shouldn’t be tied to YAML. Finance and clinical domains have their own standards. CSV is the lingua franca for tabular reference data — transaction lists, account balances, diagnostic results. Coupling corpus loading to YAML format is a framework assumption that doesn’t match the domain reality.

We extracted a CorpusLoader SPI with three implementations: YamlCorpusLoader (.yaml/.yml), JsonCorpusLoader (.json), and CsvCorpusLoader (.csv). A CompositeCorpusLoader dispatches by file extension. YamlSimulationConfig delegates to the composite, and SimulationConfigBeans produces it as a CDI bean for injection by pages.

The CSV convention uses reserved column names — _qualified_name for the method QN, _key for the lookup key, _tenancy_id for tenant isolation. Everything else becomes the output map. A finance team can drop their reference CSV into the simulation corpus pipeline without rewriting it as YAML.

What this opens up

The immediate consumer is connectors#94 — BankFeedPlatform and EmailPlatform SPIs that need full YAML-driven simulation without Java boilerplate. With parameter-name extractors and CSV corpus support, a connector author writes a simulation.yaml that reads like the SPI’s method signatures and references their domain data files directly.

The CorpusLoader SPI is also an extension point. Industry-specific formats — HL7 for clinical, FIX for trading — could plug in without touching the framework core. That’s speculative, but the boundary is clean enough that it wouldn’t be forced.


<
Previous Post
What Do You Call a Thing That Isn't Real?
>
Next Post
Agents Aren’t Connections — Why CLI Session Pools Need a Different Model