Documentation
¶
Overview ¶
Package common holds types shared across the router, queue, stream, outbox, and platform packages: messages, dispatch modes, mediation results, outbox status, configuration shapes, and warnings.
JSON tags match the wire contract exactly (camelCase, omitempty for optionals, SCREAMING_SNAKE_CASE for enums) so wire format is byte-compatible.
Index ¶
- Constants
- Variables
- func IsDeliveryRejected(status int, outcomeHeader string) bool
- type DispatchMode
- type DispatchStatus
- type EnhancedPoolMetrics
- type InFlightMessage
- type LeaderElectionConfig
- type MediationOutcome
- func CircuitOpen(delaySec int) MediationOutcome
- func Deferred(delaySec int, msg string) MediationOutcome
- func ErrorConfig(status int, msg string) MediationOutcome
- func ErrorConnection(msg string) MediationOutcome
- func ErrorProcess(delaySec int, msg string) MediationOutcome
- func PreFlightError(msg string) MediationOutcome
- func RateLimited(retryAfterSec int) MediationOutcome
- func Success(status int) MediationOutcome
- type MediationResult
- type MediationType
- type Message
- type OutboxItemType
- type OutboxStatus
- type PoolConfig
- type ProcessingTimeMetrics
- type QueueConfig
- type QueuedMessage
- type RouterConfig
- type StallConfig
- type WindowedMetrics
Constants ¶
const ( DeliveryOutcomeHeader = "FlowCatalyst-Outcome" DeliveryOutcomeReject = "reject" )
A delivery target's terminal "do not retry" answer: HTTP 422 carrying DeliveryOutcomeHeader: DeliveryOutcomeReject. Dispatch-job and scheduled-job delivery fail the attempt's job at once instead of spending the remaining retries (function runner, docs/function-runner-plan.md §13.2).
const DefaultDispatchMode = DispatchNextOnError
DefaultDispatchMode is what an unspecified mode means, everywhere: keep a message group in sequence, one at a time, and move on to the next message if one fails.
It was IMMEDIATE, which is the only mode with no ordering at all — so any producer that omitted the field silently gave up sequencing, and the loss showed only under load, where concurrent dispatch actually gets to interleave. A default that quietly weakens a guarantee is the wrong way round: ordering is cheap to opt out of (set IMMEDIATE) and expensive to discover you never had.
Variables ¶
var AllOutboxItemTypes = []OutboxItemType{OutboxItemEvent, OutboxItemDispatchJob, OutboxItemAuditLog}
AllOutboxItemTypes is the iterable set for type-aware polling loops.
Functions ¶
func IsDeliveryRejected ¶
IsDeliveryRejected reports whether a response is the terminal reject.
Types ¶
type DispatchMode ¶
type DispatchMode string
DispatchMode controls ordering behavior within a message group. Shared across platform, scheduler, and router.
const ( DispatchImmediate DispatchMode = "IMMEDIATE" DispatchNextOnError DispatchMode = "NEXT_ON_ERROR" DispatchBlockOnError DispatchMode = "BLOCK_ON_ERROR" )
func ParseDispatchMode ¶
func ParseDispatchMode(s string) DispatchMode
ParseDispatchMode maps a wire/stored value to a mode. Empty means unspecified, which is DefaultDispatchMode. Anything unrecognised is a producer bug: it also takes the default, but says so, because silently treating a typo as "no ordering" is how the previous default hid.
Deliberately still lenient — this is the one X-06 exemption, ruled X-01: unknown/absent dispatch mode defaults to NEXT_ON_ERROR rather than rejecting. Do not convert this to the (T, bool) strict shape used by ParseDispatchStatus and the rest of this package's enum parsers.
func (DispatchMode) RequiresOrdering ¶
func (d DispatchMode) RequiresOrdering() bool
RequiresOrdering reports whether the mode demands FIFO processing.
type DispatchStatus ¶
type DispatchStatus string
DispatchStatus is the lifecycle state of a dispatch job. Wire format is SCREAMING_SNAKE_CASE; matches the TypeScript SDK.
const ( DispatchPending DispatchStatus = "PENDING" DispatchQueued DispatchStatus = "QUEUED" DispatchProcessing DispatchStatus = "PROCESSING" DispatchCompleted DispatchStatus = "COMPLETED" DispatchFailed DispatchStatus = "FAILED" DispatchCancelled DispatchStatus = "CANCELLED" DispatchExpired DispatchStatus = "EXPIRED" )
func ParseDispatchStatus ¶
func ParseDispatchStatus(s string) (DispatchStatus, bool)
ParseDispatchStatus parses a stored/wire status value. IN_PROGRESS and ERROR are accepted legacy aliases of PROCESSING and FAILED — not just wire input, but real values `msg_dispatch_jobs.status` has held (see dispatchjob.GroupHoldersSQL, which still matches 'ERROR' by design so old rows keep blocking as they always did). Returns ok=false for anything else — callers MUST reject on ok=false rather than coerce an unrecognised value to PENDING (X-06: a loud read error, never a silent default — a corrupted terminal status silently reappearing as PENDING could resurrect a job that already completed or failed). Follows the (T, bool) shape of ParseOutboxItemType.
func (DispatchStatus) IsSuccessful ¶
func (s DispatchStatus) IsSuccessful() bool
IsSuccessful reports whether the status is the success terminal.
func (DispatchStatus) IsTerminal ¶
func (s DispatchStatus) IsTerminal() bool
IsTerminal reports whether a status will not change further.
type EnhancedPoolMetrics ¶
type EnhancedPoolMetrics struct {
TotalSuccess uint64 `json:"totalSuccess"`
TotalFailure uint64 `json:"totalFailure"`
TotalRateLimited uint64 `json:"totalRateLimited"`
// TotalSuppressed counts messages ACKed without delivery because their
// group was suppressed by the GroupFlushRegistry (R-53). Kept separate
// from TotalSuccess/TotalFailure: no delivery was attempted, so neither
// bucket fits, and a pool suppressing a flushed group should read
// busy-but-suppressed rather than idle.
TotalSuppressed uint64 `json:"totalSuppressed"`
SuccessRate float64 `json:"successRate"`
ProcessingTime ProcessingTimeMetrics `json:"processingTime"`
Last5Min WindowedMetrics `json:"last5Min"`
Last30Min WindowedMetrics `json:"last30Min"`
}
EnhancedPoolMetrics is the rolling-window snapshot embedded in PoolStats.Metrics.
type InFlightMessage ¶
type InFlightMessage struct {
MessageID string
BrokerMessageID string
PoolCode string
QueueIdentifier string
StartedAt time.Time
// LastSeenAt is refreshed every time the broker redelivers this message
// (receipt-handle swap). The reaper ages entries on LastSeenAt, not
// StartedAt: while the broker still holds the message it keeps
// redelivering (refreshing this), so a long-buffered entry is never
// reaped out from under the dedup; once the broker no longer has the
// message, refreshes stop and the entry ages out.
LastSeenAt time.Time
MessageGroupID string
BatchID string
ReceiptHandle string
// DeferredUntil is set while the router has handed this copy back to the
// broker for capacity (Pool.deferMsg) and expects it to return at about
// this time. The entry is KEPT, not removed, so that a second copy of the
// same message id published meanwhile — the platform's stale-QUEUED
// recovery republishes a job whose message is parked longer than its
// stale threshold, with a fresh broker id — is recognised as a duplicate
// and deleted instead of being deferred alongside it (owner, 2026-09-22:
// "500 in flight but only 200 pending — use the messageId, not the SQS
// id"). Zero while the copy is in the pipeline.
DeferredUntil time.Time
// Attempts is >0 once the message has failed at least once and is being
// retried in-pipeline. The stall detector never force-NACKs such an entry
// (a live retry owns it, and yanking it away would double-deliver), and
// the reaper exempts it from the idle bound — but only for as long as
// LastRetryAt says that retry is still live.
Attempts uint
// LastRetryAt is when this entry last recorded a new in-pipeline attempt
// (InFlightTracker.MarkRetrying, the single mutator of Attempts). It is
// what makes the reaper's Attempts>0 exemption FINITE.
//
// The exemption used to be unconditional, on the reasoning that the retry
// budget (maxInPipelineAttempts) always ends in a release that removes the
// entry. A drainer cancelled mid-retry never reaches that release — it
// re-fronts the message, clears the group's working flag and returns — so
// the entry outlived its owner and, being permanently exempt, could never
// be reaped. Every subsequent redelivery of the message was then dropped
// as a duplicate of a copy nobody owned, for ever.
//
// Zero while Attempts is 0.
LastRetryAt time.Time
}
InFlightMessage tracks a message currently being processed.
func NewInFlightMessage ¶
func NewInFlightMessage(m *Message, brokerID, queueID, batchID, receipt string) *InFlightMessage
NewInFlightMessage constructs a tracker.
func (*InFlightMessage) ElapsedSeconds ¶
func (m *InFlightMessage) ElapsedSeconds() int64
ElapsedSeconds returns how long the message has been in flight.
func (*InFlightMessage) UpdateReceiptHandle ¶
func (m *InFlightMessage) UpdateReceiptHandle(h string)
UpdateReceiptHandle replaces the receipt handle on broker redelivery and refreshes LastSeenAt (the broker evidently still holds the message).
type LeaderElectionConfig ¶
type LeaderElectionConfig struct {
Enabled bool
RedisURL string
LockKey string
LockTTLSeconds uint64
HeartbeatIntervalSeconds uint64
InstanceID string
}
LeaderElectionConfig is the unified leader-election configuration shared by the outbox and standby subsystems.
func NewLeaderElectionConfig ¶
func NewLeaderElectionConfig(redisURL string) LeaderElectionConfig
NewLeaderElectionConfig creates a config with sane defaults.
type MediationOutcome ¶
type MediationOutcome struct {
Result MediationResult
DelaySeconds int // 0 if no delay
StatusCode int // 0 if not from HTTP
ErrorMessage string
// FlushGroup is set when a 2xx body carried {"flushGroup": true}: the
// target is asking us to stop delivering this message's group and just
// ACK its siblings. DelaySeconds carries how long to suppress for.
// Honoured only alongside a successful delivery — a target that wants
// the message back cannot also discard the group.
FlushGroup bool
// PreFlight marks an outcome decided BEFORE any request went out — an
// unsupported mediation type, an unusable target URL, a payload that would
// not marshal. Such an outcome is no evidence about the target's health in
// either direction, so the circuit breaker must ignore it. Recording one as
// a success actively masks a failing endpoint: a misconfigured URL would
// hold the breaker closed on a host that is down.
//
// Set only by PreFlightError, so it cannot be forgotten at a call site.
PreFlight bool
}
MediationOutcome carries the result plus optional retry-after delay.
func CircuitOpen ¶
func CircuitOpen(delaySec int) MediationOutcome
CircuitOpen builds a circuit-open outcome: the per-endpoint breaker is open, so no HTTP call was attempted. The pool DEFERS with delaySec (the breaker's reset timeout) and, for ordered messages, marks the batch+group failed to preserve FIFO — 1:1 with the prior pool circuit-open path.
func Deferred ¶
func Deferred(delaySec int, msg string) MediationOutcome
Deferred builds an ack=false outcome: the target answered 2xx but told us to retry later ({"ack": false, "delaySeconds": N}).
func ErrorConfig ¶
func ErrorConfig(status int, msg string) MediationOutcome
ErrorConfig builds a 4xx outcome.
func ErrorConnection ¶
func ErrorConnection(msg string) MediationOutcome
ErrorConnection builds a connection-error outcome. Default delay 30s.
func ErrorProcess ¶
func ErrorProcess(delaySec int, msg string) MediationOutcome
ErrorProcess builds a 502/503/504-or-timeout ("target unavailable") outcome with optional retry delay. Every other 5xx is ErrorConfig instead — see MediationErrorProcess.
func PreFlightError ¶
func PreFlightError(msg string) MediationOutcome
PreFlightError builds a permanent rejection for a message that never reached the network. It is an ErrorConfig — the message is unusable as it stands and retrying it unchanged cannot help — flagged so the breaker stays out of it.
func RateLimited ¶
func RateLimited(retryAfterSec int) MediationOutcome
RateLimited builds a 429 outcome with the supplied Retry-After delay.
func Success ¶
func Success(status int) MediationOutcome
Success builds a successful outcome carrying the status the target actually returned.
The status is a parameter rather than a hard-coded 200 because it used to be hard-coded, and every success — 201, 202, 204 — was recorded as 200. The error and ack=false paths beside it copied the real status all along, so the logs were confidently wrong about exactly one class of response. Taking it as an argument means the compiler asks the question at each new call site; the flushGroup branch was added later and silently inherited the wrong answer.
type MediationResult ¶
type MediationResult int
MediationResult classifies the outcome of an attempt to deliver a message to its target endpoint.
const ( // MediationSuccess is a 2xx delivery — ACK. MediationSuccess MediationResult = iota // MediationErrorConfig is a 4xx — ACK to prevent infinite retries. MediationErrorConfig // MediationErrorProcess is a 502/503/504 ("target unavailable") or a // transport-level timeout — NACK for retry. Every other 5xx means the // app was reached and answered, and classifies as MediationErrorConfig // instead (permanent, like a 4xx). MediationErrorProcess // MediationErrorConnection is a connection failure — NACK for retry. MediationErrorConnection // MediationRateLimited is HTTP 429. NACK with Retry-After delay, but // don't count toward circuit-breaker failures: destination is // healthy, just throttling us. MediationRateLimited // MediationCircuitOpen means the per-endpoint breaker is open; no HTTP // call was attempted. DEFER (not a failure) until the breaker may probe. MediationCircuitOpen // MediationDeferred is a 2xx whose body carried {"ack": false}: the // target is healthy but asked us to come back later (e.g. the record is // blocked behind an earlier failure). Requeued on the pool's deferred // backoff curve, flooring at any requested delaySeconds — no in-pipeline // retry, no circuit-breaker impact. MediationDeferred )
type MediationType ¶
type MediationType string
MediationType is the kind of mediation (currently only HTTP).
const (
MediationTypeHTTP MediationType = "HTTP"
)
type Message ¶
type Message struct {
ID string `json:"id"`
PoolCode string `json:"poolCode,omitempty"`
AuthToken *string `json:"authToken,omitempty"`
SigningSecret *string `json:"signingSecret,omitempty"`
MediationType MediationType `json:"mediationType"`
MediationTarget string `json:"mediationTarget"`
MessageGroupID *string `json:"messageGroupId,omitempty"`
HighPriority bool `json:"highPriority,omitempty"`
DispatchMode DispatchMode `json:"dispatchMode,omitempty"`
}
Message is the core message structure that flows through the system. Compatible with the historical MessagePointer wire shape.
type OutboxItemType ¶
type OutboxItemType string
OutboxItemType is the kind of payload an outbox row carries.
const ( OutboxItemEvent OutboxItemType = "EVENT" OutboxItemDispatchJob OutboxItemType = "DISPATCH_JOB" OutboxItemAuditLog OutboxItemType = "AUDIT_LOG" )
func ParseOutboxItemType ¶
func ParseOutboxItemType(s string) (OutboxItemType, bool)
ParseOutboxItemType accepts case-insensitive plus hyphenated forms. Returns ok=false on unknown input.
func (OutboxItemType) APIPath ¶
func (t OutboxItemType) APIPath() string
APIPath returns the platform endpoint that consumes this item type.
type OutboxStatus ¶
type OutboxStatus int
OutboxStatus stores the integer-coded status used by the historical outbox schema. Wire string is SCREAMING_SNAKE_CASE.
const ( OutboxPending OutboxStatus = 0 OutboxSuccess OutboxStatus = 1 OutboxBadRequest OutboxStatus = 2 OutboxInternalError OutboxStatus = 3 OutboxForbidden OutboxStatus = 5 OutboxGatewayError OutboxStatus = 6 OutboxInProgress OutboxStatus = 9 )
func FromOutboxCode ¶
func FromOutboxCode(c int) OutboxStatus
FromOutboxCode maps an integer to OutboxStatus; unknown codes default to PENDING.
func (OutboxStatus) Code ¶
func (s OutboxStatus) Code() int
Code returns the integer code (for DB storage).
func (OutboxStatus) IsRetryable ¶
func (s OutboxStatus) IsRetryable() bool
IsRetryable reports whether this status will be retried by the outbox processor.
func (OutboxStatus) IsTerminal ¶
func (s OutboxStatus) IsTerminal() bool
IsTerminal reports whether this status will not be retried.
func (OutboxStatus) String ¶
func (s OutboxStatus) String() string
String returns the SCREAMING_SNAKE_CASE wire representation.
type PoolConfig ¶
type PoolConfig struct {
Code string `json:"code"`
Concurrency uint32 `json:"concurrency"`
RateLimitPerMinute *uint32 `json:"rateLimitPerMinute,omitempty"`
}
PoolConfig is the per-pool routing configuration.
type ProcessingTimeMetrics ¶
type ProcessingTimeMetrics struct {
AvgMs float64 `json:"avgMs"`
MinMs uint64 `json:"minMs"`
MaxMs uint64 `json:"maxMs"`
P50Ms uint64 `json:"p50Ms"`
P95Ms uint64 `json:"p95Ms"`
P99Ms uint64 `json:"p99Ms"`
SampleCount uint64 `json:"sampleCount"`
}
ProcessingTimeMetrics summarises latency in milliseconds with the percentiles the dashboard renders.
type QueueConfig ¶
type QueueConfig struct {
Name string `json:"queueName"`
URI string `json:"queueUri"`
Connections uint32 `json:"connections"`
VisibilityTimeout uint32 `json:"visibilityTimeout"`
}
QueueConfig is the per-queue connection configuration.
The wire contract is the camelCase shape emitted by the central config service: {queueName, queueUri, connections, visibilityTimeout}. This MUST stay interoperable — a Go router dropped into an existing deployment polls the same config service, and a Go platform serving config must be readable by existing routers. UnmarshalJSON below additionally accepts the legacy {name, uri} keys so configs produced by older Go builds still load.
func (*QueueConfig) UnmarshalJSON ¶
func (q *QueueConfig) UnmarshalJSON(data []byte) error
UnmarshalJSON accepts both the canonical camelCase keys (queueName, queueUri) and the legacy aliases (name, uri), and applies the standard defaults: name falls back to uri, connections defaults to 1, visibilityTimeout defaults to 120.
type QueuedMessage ¶
type QueuedMessage struct {
Message Message
ReceiptHandle string
BrokerMessageID string // empty if not provided
QueueIdentifier string
// BatchID is a router-assigned grouping over messages received in the
// same poll batch. It is set by the pool's
// poll loop, not the broker, and is informational only.
BatchID string
// Attempts counts how many in-pipeline mediation attempts this delivery
// has already had (0 on first dispatch). The pool increments it on each
// retry so it can recognise a re-dispatch (skip re-tracking) and grow the
// backoff. Internal-only; never crosses the wire.
Attempts uint
}
QueuedMessage is a Message received from a queue with broker tracking.
type RouterConfig ¶
type RouterConfig struct {
ProcessingPools []PoolConfig `json:"processingPools"`
Queues []QueueConfig `json:"queues"`
}
RouterConfig is what the router fetches from its config source.
type StallConfig ¶
type StallConfig struct {
Enabled bool `json:"enabled"`
StallThresholdSeconds uint64 `json:"stallThresholdSeconds"`
ForceNackStalled bool `json:"forceNackStalled"`
ForceNackAfterSeconds uint64 `json:"forceNackAfterSeconds"`
NackDelaySeconds uint32 `json:"nackDelaySeconds"`
}
StallConfig controls stall detection in the router.
func DefaultStallConfig ¶
func DefaultStallConfig() StallConfig
DefaultStallConfig returns the standard defaults.
type WindowedMetrics ¶
type WindowedMetrics struct {
SuccessCount uint64 `json:"successCount"`
FailureCount uint64 `json:"failureCount"`
RateLimitedCount uint64 `json:"rateLimitedCount"`
SuccessRate float64 `json:"successRate"`
ThroughputPerSec float64 `json:"throughputPerSec"`
ProcessingTime ProcessingTimeMetrics `json:"processingTime"`
WindowStart time.Time `json:"windowStart"`
WindowDurationSecs uint64 `json:"windowDurationSecs"`
}
WindowedMetrics is the per-window slice of pool throughput.