Part 2 of 2 · why selecting the right components is not enough
The application was wrong. Nothing had failed.
A green build. Passing tests. A running engine. A risk figure 88% too low.
A configuration change arrives after the order book is live. One published figure — a value-at-risk number — comes out 88% understated. Another is 87% understated.
There is no error code, no stack trace, and no failing instruction at which to put a breakpoint, because nothing went wrong anywhere. Five components each did precisely what they were written to do. They were called in an order that left one of them holding a stale input. Execution was, from beginning to end, completely legal.
Why "mostly right" is the wrong bar
A wrong number that announces itself is cheap: you see it, you fix it. This one does not announce itself. Finding it requires knowing in advance that it might be there.
The model was not the decisive constraint
The obvious reading is that a language model wrote sloppy code. It did not. We reran the task with an explicit warning about exactly this hazard, on a substantially more capable model. Its own report identified both the stale-value failure and the consequence of the apparent workaround:
“A
volFactorchange movesmarketdata.voland leavesrisk.varcomputed from the old vol until the next real event … the only way to fake one — re-dispatching the last tick — would advance the breach streak, the breach count and the alert count for an event that never arrived.”
It reproduced the identical error deliberately. Recomputing the stale figure through the only available event method would have published a false breach alert and inflated three regulatory counters. Having reasoned correctly about a real dilemma, it picked a horn.
That reframes the whole thing. In this fixture the decisive constraint was the API, not the model's ability to reason about the hazard — it reasoned about it correctly and was still trapped.
Stated as a working principle rather than a result from one run:
Architecture determines which behaviours a competent programmer — human or AI — can express without violating another invariant.
Give someone an API in which every correct action carries an incorrect side effect, and their competence stops being the deciding factor. They will choose a defensible wrong answer, exactly as this one did, and they will be able to justify it.
Three claims, kept separate
The series makes three related claims, but this failure demonstrates only the middle one:
- Metadata removes rediscovery. Part 1 measured fewer turns and lower weighted usage when stable component facts were made explicit.
- API shape determines what can be expressed correctly. This is what the 88% failure and the passing rerun measure, once each: without a side-effect-free recompute both available behaviours were wrong; with one, the same task passed every scored outcome.
- Derived dispatch removes ordering as separately maintained state. This is the architectural argument below. The 88% failure did not demonstrate it.
A resolver would not have prevented the 88% error. Neither would compilation: both would still have invoked the operations the library exposed, and none expressed the required behaviour. The API change fixed this measured failure; the framework did not.
Where the ordering actually lives
The ordering-drift case is a separate failure mode. It is not presented as the cause of the 88% result, and that result is not evidence that this hypothetical drift occurred.
Look at what the plain-Java engine had to get right. Five components, each with figures derived from others. Somebody — a person, or a model — decides the sequence in which they are invoked, and writes that sequence down as code:
marketData.onTick(t);
pricing.onTick(t); // reads marketData.mid
liquidity.onTick(t); // reads pricing.adjusted
risk.onTick(t); // reads marketData.vol AND liquidity.score
capital.onTick(t); // reads risk.exposure, risk.var
That ordering is a second representation of the system. The dependencies already exist — in the inputs and references each component uses — and this sequence restates them by hand. Two representations of one fact, maintained separately, and nothing checks that they agree.
They agree on the day it is written. Then a component grows a new input, or a configuration event arrives that only some components consume, and the hand-written order stops describing the real dependencies. The drift is silent because both representations are individually valid.
What a dependency graph changes
The alternative is not a better ordering discipline. It is not writing the ordering down twice.
In Fluxtion, the declared object graph captures what each component depends on. The execution sequence is not independently authored. The compiler reads those dependencies ahead of time and generates which nodes run, and in what order, for each kind of event.
The generated code is unremarkable to look at, which is the point:
if (guardCheck_mid()) isDirty_mid = mid.calc();
if (guardCheck_vol()) isDirty_vol = vol.calc();
if (guardCheck_exposure()) isDirty_exposure = exposure.calc();
Each guard is a statically generated expression over which parents changed — isDirty_notional | isDirty_vol — derived from the declared graph rather than written by a programmer. There is no list of nodes to iterate, no adjacency structure to consult at runtime. The topology was a compile-time fact and has been compiled away.
The precise claim, and it is narrower than "this cannot go wrong":
Given a correctly declared dependency graph, ordering stops being an independent piece of handwritten state that can drift from the code around it.
Declaration, not syntax
How you declare the graph matters less than people expect. Two routes exist — a Java builder, or a Spring XML file describing object construction and dependencies — and the compiler derives the same dispatch from either.
<bean id="pricing" class="com.vendor.pricing.PricingHedged">
<constructor-arg value="#{marketdata.mid}"/>
<constructor-arg value="#{marketdata.depth}"/>
</bean>
Neither file is the deliverable. Both are declarations that become generated execution code. The XML happens to be data, which means something other than a human can produce it — and that is the subject of the companion article, where a deterministic resolver writes it in 38 milliseconds with no model involved.
Two different questions, and only one of them is about execution
That companion piece is about capability resolution — matching what components provide against what an application requires. It is careful to note that this is not novel: OSGi has used manifest-declared capabilities and requirements with a resolver for many years.
But resolution and compilation answer different questions, and conflating them is how the distinction gets lost:
| Question | Output |
|---|---|
| Resolution | Which components satisfy these requirements, and can they legally be connected? → a resolved component graph — configuration |
| Compilation | Given those dependencies, what executes, and in what order, when an event arrives? → generated dispatch code |
Resolution says what should be connected. Compilation says what must happen as a consequence.
A resolver could have wired the plain-Java engine perfectly and the 88% error would still have occurred. A compiler could have derived a perfect order from the declared dependencies and still had no counter-free operation to call. The measured failure was an API-expressibility problem, not evidence for either resolution or compilation. The separate case for compilation is that, once correct operations and dependencies exist, their order need not remain a second handwritten representation.
What this does not protect you from
One qualification matters most here.
Declare the graph wrongly and you can still get stale values. In a separate experiment, wiring a bought-in component through its top-level holder object rather than the node inside it produced eight dispatch stages rather than the full graph's seventeen. It compiled. It ran. It was quiet. Those are topology stages, unrelated to the seventeen scored outcomes above.
So the failure category survives; what is removed is one specific and unusually nasty member of it — ordering as a second, separately maintained representation. That is worth having, and it is not the same as safety.
Why the perfect score is not accepted on trust
The 88% figure, its deliberate reproduction under warning, and the 17/17 after the API change come from scored experiments against scenarios the model never saw. The passing rerun's scorer was repaired before that score was accepted: it now aligns by scenario position, retains events that legitimately publish nothing, and carries the state each event had at that moment rather than back-dating final state. Four valid mutations — a wrong risk.var, a wrong carried marketdata.vol, a missing alert and a wrong breach counter — all reduced the score. A fifth planned mutation never changed its input and was recorded as invalid rather than counted as a pass.
That was not the end of the scorer story. An earlier version had reported “12 of 12 events identical” while comparing twelve events with zero. The shared scorer eventually accumulated ten explicit guards. Independent reviewers supplied five of them after demonstrating false passes for shuffled event order, an extra unexpected figure, NaN comparing equal, distinct event classes collapsed to the same simple name, and a comparison containing zero figures. Every one favoured agreement. The instrument has been attacked independently twice and both attacks found something.
The current score therefore has direct mutation evidence and guards for the false-pass classes we know about. It is stronger evidence than a green build, but it remains one before-and-after fixture, not proof that the scorer or API pattern generalises.
The two articles arrive at the same place from opposite directions. One is about not spending probabilistic computation rediscovering facts that could have been declared. This one is about not spending human or model reasoning maintaining an execution order a compiler can derive.
Intent → judgement → explicit contract → deterministic resolution → compiled execution.
Each arrow moves work to the place that has the information, and reserves probabilistic reasoning for the parts that are not mechanically derivable — beginning with deciding what the business actually meant.
Which is the whole argument in five words:
Intent is authored. Consequences are derived.
Experiment archive: this is a working repository, not a frozen snapshot of the evidence at publication. Files and results may move or change as the experiments and independent review continue. It contains the fixtures, predictions committed before each run, generated sources and notes recording what each round got wrong. One fixture family, one problem domain — the comparison arm is a single run, and its library was changed between the failing and passing attempts, which is the point rather than a confound.