SP0.9: sim-thread applier draining ActuatorCommandQueue via actuator ports#737
Merged
Merged
Conversation
…uator ports
- Add ControlStepListener fun interface to :core/sim — generic hook called on the
kDisco sim thread at the start of each ShuntingLoop iteration; no dispatcher
logic in :core (satisfies option-2 gate criterion).
- Modify ShuntingLoop:
- `var controlStepListener: ControlStepListener?` — set by wiring layer before
context.run(); null by default so all existing callers are unaffected.
- `fun approveQueuedTrain(trainId)` — public delegate to applyApproveTrain,
used by DispatchDecisionApplier's onApproveTrain callback.
- `controlStepListener?.onControlStep()` called at top of iteration() to drain
and apply any pending queue decisions before the inline dispatcher runs.
- Add DispatchDecisionApplier to :dispatcher-agent — implements ControlStepListener;
drains ActuatorCommandQueue and routes each DispatchDecision:
ApproveTrain → onApproveTrain callback,
ReservePath → NetworkActuatorPort.requestRoute,
NoAction → no-op.
- Add DispatchDecisionApplierTest (14 tests) covering decision routing, FIFO
order, empty-queue no-ops, all RouteRequestResult outcomes, and four
thread-identity assertions asserting actuator calls and the approve callback
always execute on the onControlStep thread (not the posting/driver thread).
- Add io.mockk:mockk to :dispatcher-agent test dependencies.
Closes #731.
Copilot
AI
changed the title
[WIP] Implement mechanism for draining ActuatorCommandQueue
SP0.9: sim-thread applier draining ActuatorCommandQueue via actuator ports
Jul 9, 2026
Addresses the two Important issues from the PR #737 code review: * Add ShuntingLoopControlStepListenerTest (:core jvmTest, integration-tagged): - proves controlStepListener.onControlStep() is invoked exactly once per iteration, before the admission Dispatcher.decide() call (the load-bearing ordering invariant the SP0.9 design rests on) — endTime=0L gives one iteration; a custom Dispatcher records that the listener already ran. - proves approveQueuedTrain delegates to applyApproveTrain and moves a queued train into the approved set — a NoAction-only dispatcher makes the callback the only approval path; the listener consumes the captured id once (getAndSet(null)) so re-approval cannot throw in applyApproveTrain's requireNotNull and crash the kDisco process mid-iteration. Closes the conservative-sim test-coverage gap (the iteration() seam had no direct test; DispatchDecisionApplierTest only exercised onControlStep on the test thread). * Make the two `when` constructs in DispatchDecisionApplier compile-enforced exhaustive over the sealed DispatchDecision / RouteRequestResult types (expression-body `when`), matching DefaultNetworkActuatorPort.requestRoute. A future subtype (e.g. HoldTrain in SP2b, #556) is now a compile error instead of a silently-dropped decision. Also records the Minor #4 coupling observation (ApproveTrain-via-callback) as a code comment in DispatchDecisionApplier's onApproveTrain KDoc and as a comment on GitHub issue #556, recommending a TrainAdmissionPort extraction for SP2b train-lifecycle commands. Verified: :core:integrationTest 2/2, :dispatcher-agent:test + :core:jvmTest 2177/2177, detekt + ktlintCheck clean. Co-Authored-By: Claude <noreply@anthropic.com>
bedaHovorka
approved these changes
Jul 9, 2026
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Drains
ActuatorCommandQueue(SP0.8) on the kDisco sim thread and applies eachDispatchDecisionthrough the SP0.6 actuator ports. All simulation mutation stays on the sim thread.Mechanism choice: option 2 (generic
ControlStepListenerin:core)Option 1 (standalone kDisco
Processin:dispatcher-agent) is infeasible:Process.activate()must be called from the sim thread, but:corecannot import:dispatcher-agenttypes (circular dependency). AControlStepListenerinterface in:coreavoids the coupling —:dispatcher-agentimplements it;:coreonly knows the interface.:corechangesControlStepListener— newfun interfacewith singleonControlStep(), placed incore/sim/. No dispatcher semantics, purely a sim-thread callback hook.ShuntingLoop— three additions:var controlStepListener: ControlStepListener?— nullable field set by the wiring layer beforecontext.run(); null by default so all existing callers are unaffectedfun approveQueuedTrain(trainId)— public delegate to the existing privateapplyApproveTrain, callable from the applier's callbackcontrolStepListener?.onControlStep()at the top ofiteration(), beforebuildAdmissionObservation(), so drained decisions are visible to the inline dispatcher:dispatcher-agentchangesDispatchDecisionApplier— implementsControlStepListener; drains the queue and routes each decision:DispatchDecisionApplierTest— 14 tests: decision routing per subtype, FIFO drain order, empty-queue no-ops, allRouteRequestResultoutcomes, and four thread-identity assertions verifying that actuator calls and the approve callback execute on theonControlStepcaller thread (never the posting/driver thread).