Some checks failed
ci / build (push) Failing after 23s
Completes the request path for FLUID-WP-0003 T01-T03 and T05-T07. The gateway resolves a revision, routes to the adapter process, and records what happened, without ever depending on the control plane to serve. Three behaviours carry tests because the architecture rests on them: Emit never blocks against a stalled sink (Blueprint 34.2), the gateway keeps serving after control-plane loss (invariant 2), and backend internals do not leak into error responses (5.7). The connector retries only idempotent methods, so a slow adapter cannot cause a hall-of-helix entry to be published twice, and breakers are per-revision so a broken candidate does not take the stable revision down with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014KmVxhJ35tCo7rE7UnLwWu Assistant: claude-code Assistant-Model: opus Assistant-Process: 1116572@bnt-lap001 Assistant-Session: 8ba9bb93-a72a-4883-b189-2499cce5c400
91 lines
3.2 KiB
Go
91 lines
3.2 KiB
Go
package runtime
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/tegwick/fluid-core/internal/contract"
|
|
)
|
|
|
|
// ErrorKind classifies a response the interface produced rather than the
|
|
// backend. These map onto the telemetry error classes so that a response and
|
|
// the evidence it generates cannot disagree.
|
|
type ErrorKind string
|
|
|
|
const (
|
|
ErrorValidation ErrorKind = "validation"
|
|
ErrorUnknownPath ErrorKind = "unknown_path"
|
|
ErrorUnsupported ErrorKind = "unsupported_parameter"
|
|
ErrorAuthorization ErrorKind = "authorization"
|
|
ErrorUnavailable ErrorKind = "backend_failure"
|
|
ErrorTimeout ErrorKind = "timeout"
|
|
ErrorPolicy ErrorKind = "policy_rejection"
|
|
ErrorNoCapability ErrorKind = "missing_capability"
|
|
)
|
|
|
|
// ErrorBody is the interface's error representation.
|
|
//
|
|
// ArchitectureBlueprint.md section 5.7: errors should carry enough structured
|
|
// information to improve observability without leaking backend detail, and may
|
|
// include a correlation reference that lets downstream analysis link the
|
|
// response a consumer saw to the pressure it generated.
|
|
type ErrorBody struct {
|
|
Kind ErrorKind `json:"kind"`
|
|
// Message is written for the consumer and must stay free of backend
|
|
// internals: hostnames, stack traces, driver errors, upstream payloads.
|
|
Message string `json:"message"`
|
|
// Correlation is the reference a consumer can quote back, and the key that
|
|
// ties this response to its telemetry.
|
|
Correlation string `json:"correlation"`
|
|
// Revision tells the consumer which contract answered. Without it a client
|
|
// debugging an unexpected response has no way to know what it was talking to.
|
|
Revision contract.RevisionID `json:"revision,omitempty"`
|
|
// Field names the offending input for validation failures.
|
|
Field string `json:"field,omitempty"`
|
|
// Feedback points at the explicit-feedback endpoint. Turning a dead end into
|
|
// an invitation is the cheapest pressure signal the interface can collect.
|
|
Feedback string `json:"feedback,omitempty"`
|
|
}
|
|
|
|
// statusFor maps an error kind to its HTTP status.
|
|
var statusFor = map[ErrorKind]int{
|
|
ErrorValidation: http.StatusBadRequest,
|
|
ErrorUnknownPath: http.StatusNotFound,
|
|
ErrorUnsupported: http.StatusBadRequest,
|
|
ErrorAuthorization: http.StatusForbidden,
|
|
ErrorUnavailable: http.StatusBadGateway,
|
|
ErrorTimeout: http.StatusGatewayTimeout,
|
|
ErrorPolicy: http.StatusForbidden,
|
|
ErrorNoCapability: http.StatusNotImplemented,
|
|
}
|
|
|
|
// ResponsePolicy renders interface errors consistently.
|
|
type ResponsePolicy struct {
|
|
// FeedbackPath, when set, is advertised on every error.
|
|
FeedbackPath string
|
|
}
|
|
|
|
// WriteError renders an error response.
|
|
func (p ResponsePolicy) WriteError(w http.ResponseWriter, kind ErrorKind, correlation, message string, rev contract.RevisionID, field string) {
|
|
status, ok := statusFor[kind]
|
|
if !ok {
|
|
status = http.StatusInternalServerError
|
|
}
|
|
|
|
body := ErrorBody{
|
|
Kind: kind,
|
|
Message: message,
|
|
Correlation: correlation,
|
|
Revision: rev,
|
|
Field: field,
|
|
Feedback: p.FeedbackPath,
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("X-FLUID-Correlation", correlation)
|
|
if rev != "" {
|
|
w.Header().Set("X-FLUID-Revision", string(rev))
|
|
}
|
|
w.WriteHeader(status)
|
|
_ = json.NewEncoder(w).Encode(body)
|
|
}
|