ADR-0006: HTTP store ↔ proposal validator stays event-driven¶
Status¶
Accepted.
Context¶
pkg/controller/httpstore.Component runs a refresh-timer loop: per registered URL, when the refresh interval elapses it fetches the URL, and on a content change publishes ProposalValidationRequestedEvent. pkg/controller/proposalvalidator.Component consumes that event, runs render-validate against the proposed content via its HTTPOverlay, and publishes ProposalValidationCompletedEvent. httpstore correlates the response by RequestID against its pendingValidationID, then either promotes the pending content (HTTPResourceAcceptedEvent) or discards it (HTTPResourceRejectedEvent).
That shape is suspicious on first inspection — one publisher, one subscriber-back, closed loop. ADR-0001 removed an analogous event hop for the renderer. A future architecture review will likely propose collapsing this one too: drop the events, have httpstore call proposalValidator.ValidateSync(ctx, overlays) directly the way dryrunvalidator already does.
Decision¶
The event hop stays. httpstore continues to publish ProposalValidationRequestedEvent and react to ProposalValidationCompletedEvent; it does not call ValidateSync directly.
Consequences¶
- The refresh-timer goroutine never blocks on validation. Render+validate can take seconds (template rendering plus three-phase HAProxy validation); converting to a direct call would either block the timer loop for that duration or require a goroutine + result channel — i.e. reinventing the bus.
httpstoreis already deeply on the bus for unrelated reasons: it publishesHTTPResourceUpdated/HTTPResourceAccepted/HTTPResourceRejected(consumed by the commentator and metrics) andReconciliationTriggered(consumed by the reconciler). Carving out only the proposal-validator hop would create local inconsistency inside the component, which is the opposite of what we want.- The cost is real: ~50 LOC of
pendingValidationIDmutex juggling, plus three event types kept alive. We accept that cost in exchange for the non-blocking refresh loop.
Do not re-suggest¶
A future architecture review applying ADR-0001's "no event hop without a second participant" principle will notice that the publisher/subscriber loop here has exactly one of each. Do not propose collapsing it to a synchronous call. The async coupling is load-bearing — it decouples refresh-timer cadence from validation latency. ADR-0001 applies to event hops that exist for symmetry; this hop exists for asynchrony. Different problem.
If you have a concrete use case that requires synchronous coupling here (e.g. a redesign that eliminates the refresh-timer loop), propose a new ADR superseding this one with the requirement.
Related¶
- ADR-0001 — establishes the "no event hop without a second participant" principle that this ADR explicitly carves an exception to.
- The manual
pendingValidationIDcorrelation inhttpstore.handleProposalValidationCompletedreinvents request/response correlation.EventBus.Request()is the wrong tool (it's synchronous-blocking); what's missing frompkg/eventsis an async correlation primitive — "publish a request, react to its response on my own event loop." A future change introducing such a primitive would simplifyhttpstorewithout touching this ADR's decision.