feat(kernel,events): freestyle graph re-routing — deterministic override core

Implements the replay-safe half of preemptive redirect (graph re-routing). An
operator-confirmed PreemptRedirectEvent overrides the resolver's edge at the next
transition boundary; the pure resolver stays untouched.

- PreemptRedirectEvent / PreemptRedirectBlockedEvent (+ serialization registration)
- TransitionExecutedEvent + TransitionDecision.Move gain optional redirectId — the
  durable 'consumed once' marker
- PreemptRedirect.decide: pure decision over the event log (override / block / none),
  so replay reaches the identical outcome without re-classifying (invariant #8)
- override wired in DefaultSessionOrchestrator.step above resolveTransition; back-edge
  jumps count against the existing maxRetries cap via executeMove
- blocks jumps to unknown stages or targets with unsatisfied needs
- DomainEventMapper: redirect events mapped to null (operator surface ships with the
  LLM-proposal + approval-confirm front-half)
- tests: override+consume-once, block-on-needs, block-on-unknown, ignore-consumed

Front-half (LLM proposal -> approval-gate confirm -> emit PreemptRedirectEvent) is
deferred; needs live LLM verification. Until it ships no redirect event is emitted in
production, so the override is inert. Spec: docs/plans/2026-06-10-freestyle-graph-rerouting.md
This commit is contained in:
2026-06-10 11:51:47 +04:00
parent bc83a2d64e
commit bb70c94a99
9 changed files with 459 additions and 4 deletions
@@ -23,6 +23,8 @@ import com.correx.core.events.events.ModelLoadedEvent
import com.correx.core.events.events.ModelUnloadedEvent
import com.correx.core.events.events.OrchestrationPausedEvent
import com.correx.core.events.events.OrchestrationResumedEvent
import com.correx.core.events.events.PreemptRedirectBlockedEvent
import com.correx.core.events.events.PreemptRedirectEvent
import com.correx.core.events.events.StageCompletedEvent
import com.correx.core.events.events.StageFailedEvent
import com.correx.core.events.events.StoredEvent
@@ -254,6 +256,11 @@ suspend fun domainEventToServerMessage(
// Internal slot→CAS-hash bookkeeping (F-007 durable bridge); no operator-facing surface.
is ArtifactContentStoredEvent -> null
// Freestyle graph-rerouting bookkeeping. The deterministic record is in place; a dedicated
// operator surface ships with the LLM-proposal + approval-confirm front-half.
is PreemptRedirectEvent -> null
is PreemptRedirectBlockedEvent -> null
else -> {
log.debug(
"DomainEventMapper: unmapped payload type={} sessionId={} sequence={}",