Propagate correlation_id/causation_id through execution events, add API version header

Execution events (started/failed/succeeded) never carried the
correlation/causation IDs from the ExecuteRequest, even though the
fields existed on Execution. Added CausationID to ExecuteRequest and
Execution, and split emitEvent into a correlated variant used
throughout the execution lifecycle. handleExecute falls back to
X-Correlation-ID/X-Causation-ID headers when the body omits them.
Also add X-Hexis-Version negotiation on /api/v1/*, matching Nexus.

Part of Vikunja #273.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ghELqYhZNLub2TXGMazqA
This commit is contained in:
kami
2026-07-20 11:11:49 +04:00
parent 89d8433d17
commit ed593efb71
5 changed files with 390 additions and 14 deletions
+38
View File
@@ -10,6 +10,35 @@ import (
"time"
)
// APIVersion is sent as X-Hexis-Version on every request so the server can
// negotiate/reject unsupported client versions.
const APIVersion = "v1"
type correlationKey struct{}
type causationKey struct{}
// WithCorrelationID returns a context that carries a correlation ID to be
// sent as X-Correlation-ID on every Hexis request made with it.
func WithCorrelationID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, correlationKey{}, id)
}
// WithCausationID returns a context that carries a causation ID to be sent
// as X-Causation-ID on every Hexis request made with it.
func WithCausationID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, causationKey{}, id)
}
func correlationIDFrom(ctx context.Context) string {
id, _ := ctx.Value(correlationKey{}).(string)
return id
}
func causationIDFrom(ctx context.Context) string {
id, _ := ctx.Value(causationKey{}).(string)
return id
}
type Client struct {
baseURL string
httpClient *http.Client
@@ -37,6 +66,13 @@ func (c *Client) do(ctx context.Context, method, path string, body, result any)
return fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Hexis-Version", APIVersion)
if id := correlationIDFrom(ctx); id != "" {
req.Header.Set("X-Correlation-ID", id)
}
if id := causationIDFrom(ctx); id != "" {
req.Header.Set("X-Causation-ID", id)
}
resp, err := c.httpClient.Do(req)
if err != nil {
@@ -83,6 +119,7 @@ type Execution struct {
Error string `json:"error,omitempty"`
RequestedBy map[string]string `json:"requested_by,omitempty"`
CorrelationID string `json:"correlation_id,omitempty"`
CausationID string `json:"causation_id,omitempty"`
IdempotencyKey string `json:"idempotency_key,omitempty"`
}
@@ -94,6 +131,7 @@ type ExecuteRequest struct {
Origin map[string]string `json:"origin,omitempty"`
IdempotencyKey string `json:"idempotency_key,omitempty"`
CorrelationID string `json:"correlation_id,omitempty"`
CausationID string `json:"causation_id,omitempty"`
}
type CreateCapabilityRequest struct {