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 with one immutable pending-version batch, then either promotes those versions and publishes HTTPResourceAcceptedEvent, or discards them and logs the rejection directly.
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: explicit request/batch correlation 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 batch correlation in
httpstore.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.