Documentation
¶
Index ¶
- Constants
- func APIVersionUnaryServerInterceptor() grpc.UnaryServerInterceptor
- func ClientIPUnaryServerInterceptor() grpc.UnaryServerInterceptor
- func ConvertAPIErrorToGRPC(apiErr *apierror.APIError) error
- func ConvertGRPCError(ctx context.Context, err error, serviceName string) *apierror.APIError
- func GetAPIVersionFromMetadata(md metadata.MD) string
- func GetClientIPFromMetadata(md metadata.MD) string
- func GetIdentityFromMetadata(md metadata.MD) (*types.Identity, *apierror.APIError)
- func GetRequestIDFromMetadata(md metadata.MD) string
- func IdempotencyKeyUnaryServerInterceptor() grpc.UnaryServerInterceptor
- func IdentityUnaryServerInterceptor() grpc.UnaryServerInterceptor
- func IsIdempotentReplayed(md metadata.MD) bool
- func NewMissingGRPCRequestDataError() error
- func RecoveryUnaryInterceptor() grpc.UnaryServerInterceptor
- func RequestIDUnaryServerInterceptor() grpc.UnaryServerInterceptor
- func SetAPIVersionInMetadata(md metadata.MD, version string)
- func SetIdempotentReplayed(ctx context.Context)
- func SetIdentityInMetadata(md metadata.MD, identity *types.Identity)
- func WithIdempotencyTracking(ctx context.Context) (context.Context, func())
- type AmqpMessage
- type AmqpRoutingKey
- type DocumentedType
- type GRPCClientConfig
- type GRPCClientConn
- type GRPCConnTarget
- type GRPCServer
- type GRPCServerConfig
- type RunCompleteData
- type WSMessage
Examples ¶
Constants ¶
const ( // IdentityHeader is the header name for the identity in the metadata IdentityHeader = "identity" // IdempotencyKeyHeader is the header name for the raw idempotency key string in the metadata IdempotencyKeyHeader = "idempotency-key" // IdempotencyKeyIDHeader is the header name for the idempotency key database ID in the metadata IdempotencyKeyIDHeader = "idempotency-key-id" // APIVersionHeader is the header name for the API version in the metadata APIVersionHeader = "api-version" // RequestIDHeader is the header name for the request ID in the metadata RequestIDHeader = "request-id" // ClientIPHeader is the header name for the originating HTTP client IP when the API gateway forwards a user request to a backend service. ClientIPHeader = "client-ip" )
const ( // IdempotentReplayedHeader is the gRPC response metadata key that signals a cached (replayed) response. Clients can inspect this to know whether the server executed the request or returned a stored result. IdempotentReplayedHeader = "x-idempotent-replayed" // IdempotentReplayedHeaderValue is the value set on IdempotentReplayedHeader when the response was served from cache. IdempotentReplayedHeaderValue = "true" )
const ( WSTypeSubscribe = "subscribe" WSTypeUnsubscribe = "unsubscribe" WSTypePing = "ping" WSTypeRunEvent = "run_event" WSTypeRunComplete = "run_complete" WSTypeError = "error" WSTypePong = "pong" // Messaging / notifications — client → server WSTypeSubscribeUser = "subscribe_user" WSTypeSubscribeConversation = "subscribe_conversation" WSTypeUnsubscribeConversation = "unsubscribe_conversation" WSTypeCatchup = "catchup" WSTypeMarkRead = "mark_read" // Messaging / notifications — server → client WSTypeNotification = "notification" WSTypeMessage = "message" WSTypeConversationUpdated = "conversation_updated" WSTypeUnread = "unread" WSTypeTyping = "typing" WSTypeAccountUnreadHint = "account_unread_hint" WSTypeCatchupBatch = "catchup_batch" // WSTypeAgentRunStarted signals on a conversation topic that an agent participant's chat-triggered run has begun; it carries the run id so the client can subscribe to that run's live step stream // (run:<id>) and render the agent's interim work inline in the thread. WSTypeAgentRunStarted = "agent_run_started" )
WebSocket message type constants.
const (
// GRPCPort is the standard port all backend gRPC servers listen on inside the cluster.
GRPCPort = 9092
)
Variables ¶
This section is empty.
Functions ¶
func APIVersionUnaryServerInterceptor ¶
func APIVersionUnaryServerInterceptor() grpc.UnaryServerInterceptor
APIVersionUnaryServerInterceptor extracts the API version from gRPC metadata and adds it to the context.
func ClientIPUnaryServerInterceptor ¶
func ClientIPUnaryServerInterceptor() grpc.UnaryServerInterceptor
ClientIPUnaryServerInterceptor extracts the client IP from gRPC metadata and adds it to the context for downstream use (e.g. audit events).
func ConvertAPIErrorToGRPC ¶
ConvertAPIErrorToGRPC encodes an APIError as a gRPC status error. The full APIError JSON is embedded in the status message (prefixed with apiErrorMarker) so that ConvertGRPCError can reconstruct it losslessly. The gRPC status code is chosen to match the APIError category.
func ConvertGRPCError ¶
ConvertGRPCError translates a gRPC status error into an APIError. If the status message contains an encoded APIError (prefixed with apiErrorMarker) the original error is reconstructed losslessly. Otherwise the gRPC code is mapped to a generic APIError.
ctx should be the original HTTP request context so that client disconnections (context.Canceled on the request) can be distinguished from server-side timeouts.
func GetAPIVersionFromMetadata ¶
GetAPIVersionFromMetadata extracts the API version from the metadata.
func GetClientIPFromMetadata ¶
GetClientIPFromMetadata extracts the client IP from gRPC metadata.
func GetIdentityFromMetadata ¶
GetIdentityFromMetadata extracts the caller's identity from gRPC incoming metadata. Returns an APIError if the header is missing, duplicated, or malformed.
func GetRequestIDFromMetadata ¶
GetRequestIDFromMetadata extracts the request ID from the metadata.
func IdempotencyKeyUnaryServerInterceptor ¶
func IdempotencyKeyUnaryServerInterceptor() grpc.UnaryServerInterceptor
IdempotencyKeyUnaryServerInterceptor returns a server interceptor that extracts the idempotency key and the handler's full method name from incoming metadata and stores them in the context.
func IdentityUnaryServerInterceptor ¶
func IdentityUnaryServerInterceptor() grpc.UnaryServerInterceptor
IdentityUnaryServerInterceptor returns a server interceptor that extracts the caller's identity from incoming gRPC metadata and stores it in the context. If the header is absent or invalid the request proceeds without an identity.
func IsIdempotentReplayed ¶
IsIdempotentReplayed checks if the idempotent replayed header is set in the gRPC metadata.
func NewMissingGRPCRequestDataError ¶
func NewMissingGRPCRequestDataError() error
NewMissingGRPCRequestDataError returns a gRPC-encoded invariant violation for a nil request payload.
func RecoveryUnaryInterceptor ¶
func RecoveryUnaryInterceptor() grpc.UnaryServerInterceptor
RecoveryUnaryInterceptor recovers from panics in gRPC unary handlers and converts them to internal server errors with details.
func RequestIDUnaryServerInterceptor ¶
func RequestIDUnaryServerInterceptor() grpc.UnaryServerInterceptor
RequestIDUnaryServerInterceptor extracts the request ID from gRPC metadata and adds it to the context.
func SetAPIVersionInMetadata ¶
SetAPIVersionInMetadata sets the API version in the metadata.
func SetIdempotentReplayed ¶
SetIdempotentReplayed sets the idempotent replayed header on the gRPC response.
func SetIdentityInMetadata ¶
SetIdentityInMetadata serializes the identity as JSON and sets it on the outgoing gRPC metadata. Silently drops the value if marshalling fails.
func WithIdempotencyTracking ¶
WithIdempotencyTracking sets up idempotency response tracking for a gRPC handler. It returns the updated context and a finalize function that should be deferred. The finalize function will set the appropriate gRPC header if the response was replayed.
Usage:
ctx, finalizeIdempotency := contracts.WithIdempotencyTracking(ctx) defer finalizeIdempotency()
Types ¶
type AmqpMessage ¶
type AmqpMessage struct {
// Identity is the authenticated caller that triggered the message.
Identity *types.Identity `json:"identity"`
// Data is the application-specific payload (typically JSON-encoded).
Data []byte `json:"data"`
// MessageID uniquely identifies this message instance.
MessageID string `json:"message_id,omitempty"`
// RequestID is the originating HTTP request ID for end-to-end tracing.
RequestID string `json:"request_id,omitempty"`
// ParentMessageID links this message to the message that caused it, forming a causal chain.
ParentMessageID string `json:"parent_message_id,omitempty"`
// OperationID groups related messages that belong to the same logical operation.
OperationID string `json:"operation_id,omitempty"`
// IdempotencyKey is forwarded from the HTTP layer so consumers can deduplicate processing.
IdempotencyKey string `json:"idempotency_key,omitempty"`
// Step identifies the current stage in a multi-step workflow.
Step string `json:"step,omitempty"`
}
AmqpMessage is the envelope for all messages published to and consumed from RabbitMQ. It carries the caller's identity, the application payload, and tracing/idempotency metadata needed to correlate and deduplicate messages.
type AmqpRoutingKey ¶
type AmqpRoutingKey string
AmqpRoutingKey is the routing key for AMQP.
Follows the pattern: <service_or_domain>.<category>.<action>
const ( // NotificationCmdSendEmail is a command to send an email notification to a user. NotificationCmdSendEmail AmqpRoutingKey = "notification.cmd.send_email" // NotificationEventEmailSent is an event that indicates that an email has been sent successfully. NotificationEventEmailSent AmqpRoutingKey = "notification.event.email_sent" // NotificationEventEmailFailed is an event that indicates that an email has failed to send. NotificationEventEmailFailed AmqpRoutingKey = "notification.event.email_failed" // NotificationCmdFanout instructs notification-service to turn an alert/message intent into a message plus per-recipient notification rows (system/agent/event alerts, broadcasts). NotificationCmdFanout AmqpRoutingKey = "notification.cmd.fanout" // NotificationCmdSendMessage is the async chat-send path used for heavy group/broadcast fan-out. NotificationCmdSendMessage AmqpRoutingKey = "notification.cmd.send_message" // NotificationCmdAgentReply instructs notification-service to post an agent's reply into a conversation as the agent participant (attributed + linked to the producing run). Emitted by agent-service after a chat-triggered run. Carries a Phase ("start" creates the streaming row, // "final"/empty finalizes it) so the reply renders as one record that streams in. NotificationCmdAgentReply AmqpRoutingKey = "notification.cmd.agent_reply" // NotificationCmdAgentReplyPatch streams a partial body into an in-flight agent reply message while the run is still producing tokens. Best-effort (published straight to the exchange, not the outbox): patches are lossy by design — they carry the full accumulated body (last-write-wins) and // NotificationCmdAgentReply's "final" phase reconciles the persisted row. NotificationCmdAgentReplyPatch AmqpRoutingKey = "notification.cmd.agent_reply_patch" // NotificationEventDelivered carries a best-effort realtime push to the gateways (bell + live chat). NotificationEventDelivered AmqpRoutingKey = "notification.event.delivered" // NotificationEventConversationUpdated carries unread/last-message/typing updates to the gateways. NotificationEventConversationUpdated AmqpRoutingKey = "notification.event.conversation_updated" // CoreCmdPurgeAccountData is a command to purge all account-scoped data for a deleted sandbox account. CoreCmdPurgeAccountData AmqpRoutingKey = "core.cmd.purge_account_data" // CoreCmdSeedSandbox is a command to populate a sandbox account with tutorial seed data. CoreCmdSeedSandbox AmqpRoutingKey = "core.cmd.seed_sandbox" // CoreCmdExecuteProductionStep is a command to execute production step side-effects (inventory updates, reservation management) after a batch mutation such as initialize, move, merge, or split. CoreCmdExecuteProductionStep AmqpRoutingKey = "core.cmd.execute_production_step" // CoreCmdUndoBatchScan is a command to reverse the inventory a scan recorded against a batch that has just been deleted: the receipts it produced, the issues it consumed, and the reservations it drew down. The delete itself is synchronous; this unwinds the ledger behind it. CoreCmdUndoBatchScan AmqpRoutingKey = "core.cmd.undo_batch_scan" // CoreCmdSyncStripeCustomer is a command to reconcile a customer with the account's connected Stripe integration: create the Stripe customer on first sync, or push a changed email/name/number onto the existing one. Published by customer create/update so a Stripe outage can never fail the mutation that triggered it. CoreCmdSyncStripeCustomer AmqpRoutingKey = "core.cmd.sync_stripe_customer" // runs the read-only matching pass of a HubSpot backfill job out-of-band so the triggering request returns immediately. CoreCmdHubspotSyncPreview AmqpRoutingKey = "core.cmd.hubspot_sync_preview" // runs the write phase of a reviewed HubSpot backfill job. CoreCmdHubspotSyncExecute AmqpRoutingKey = "core.cmd.hubspot_sync_execute" // asks the core-service to solve and persist one production schedule version. Published by the generation cadence, which only enqueues: a solve takes minutes, and running it inside the scheduler lease would block every other account behind whichever one is solving. CoreCmdGenerateProductionSchedule AmqpRoutingKey = "core.cmd.generate_production_schedule" // indicates a schedule version was published and its first weeks frozen. Consumers notify the departments that now have a committed plan to work to. CoreEventProductionSchedulePublished AmqpRoutingKey = "core.event.production_schedule_published" // indicates a sales order was created. Consumers use it to run out-of-band side effects (e.g. syncing the order to a third-party CRM such as HubSpot) without blocking the create response. CoreEventSalesOrderCreated AmqpRoutingKey = "core.event.sales_order_created" // CoreEventSalesOrderShippingUpdated indicates a sales order's carrier, service level, or ship-to address changed. The core-service consumer re-syncs the order's existing shipment records to match, out-of-band from the update response. CoreEventSalesOrderShippingUpdated AmqpRoutingKey = "core.event.sales_order_shipping_updated" // CoreEventCustomerRegistered indicates a buyer completed registration on a seller's customer portal (a brand-new customer account or a new login joining an existing one). The notification-service consumer notifies the seller's customer-service support-route group so they can follow up. CoreEventCustomerRegistered AmqpRoutingKey = "core.event.customer_registered" // CoreEventInventoryReceived states that stock of an item became available. Allocation is one reaction to it: an issue that went short because the shelf could not cover it is filled the moment what it was waiting for arrives, rather than at whatever hour a sweep happens to run. CoreEventInventoryReceived AmqpRoutingKey = "core.event.inventory_received" // CoreEventItemCostBasisChanged states that something an item's cost is derived from moved — the unit cost of a material, or the make-up of a production step. Costing reacts by recomputing every item downstream of the change, since a cost is only as current as the inputs it was last calculated from. CoreEventItemCostBasisChanged AmqpRoutingKey = "core.event.item_cost_basis_changed" // CoreEventBatchScanned states that a batch was scanned at a station: the unit of work exists and carries the measures the operator recorded. It says nothing about what should follow, so a subscriber that wants to move inventory, credit the schedule, or notify a department binds its own queue and decides for itself. // // This supersedes CoreCmdExecuteProductionStep, which named one particular reaction and therefore needed a second message to carry seconds and waste. The scan is a single fact, so it is published once and its subscribers derive the rest. CoreEventBatchScanned AmqpRoutingKey = "core.event.batch_scanned" // LoggingEventRequestLogged is an event that indicates that a request has been logged. LoggingEventRequestLogged AmqpRoutingKey = "logging.event.request_logged" // BillingEventStripeWebhook is an event carrying a verified Stripe webhook payload for asynchronous processing. BillingEventStripeWebhook AmqpRoutingKey = "billing.event.stripe_webhook" // AgentCmdExecuteRun is a command to execute an agent run. AgentCmdExecuteRun AmqpRoutingKey = "agent.cmd.execute_run" // AgentCmdExecuteAction is a command to execute a proposed agent action. AgentCmdExecuteAction AmqpRoutingKey = "agent.cmd.execute_action" // AgentCmdContinueRun is a command to continue an agent run awaiting input. AgentCmdContinueRun AmqpRoutingKey = "agent.cmd.continue_run" // AgentCmdChatRun starts an agent run from a chat message: agent-service creates a chat-linked run // (conversation_id + trigger_message_id) and executes it. Emitted by notification-service when an agent participant's trigger policy fires. AgentCmdChatRun AmqpRoutingKey = "agent.cmd.chat_run" // AgentEventRunCompleted is an event indicating an agent run has finished. AgentEventRunCompleted AmqpRoutingKey = "agent.event.run_completed" // AgentEventRunStep is an event carrying a single run step for real-time WebSocket streaming to the frontend. AgentEventRunStep AmqpRoutingKey = "agent.event.run_step" // BillingCmdSyncSeats is a command to synchronize seat counts with the billing provider after account user changes. BillingCmdSyncSeats AmqpRoutingKey = "billing.cmd.sync_seats" // BillingCmdReportSeatChange is a command to report a seat count change to the billing provider's usage metering system. BillingCmdReportSeatChange AmqpRoutingKey = "billing.cmd.report_seat_change" // Commands the billing provider's usage metering system to record a created invoice. BillingCmdReportInvoiceCreated AmqpRoutingKey = "billing.cmd.report_invoice_created" // PlatformEventAuditLogged is an event that indicates an audit event has been produced and needs to be persisted by the platform-service. PlatformEventAuditLogged AmqpRoutingKey = "platform.event.audit_logged" )
type DocumentedType ¶
type DocumentedType interface {
SchemaExample() any // Returns an example of the type
}
DocumentedType is an interface that types must implement to specify their documentation type.
type GRPCClientConfig ¶
type GRPCClientConfig struct {
// KeepaliveParams (optional; default: 60s ping, 5s timeout) controls how often the client pings the server and how long it waits for a response before considering the connection dead.
KeepaliveParams keepalive.ClientParameters
// UnaryInterceptors (optional; default: retry-on-transient) is the chain of client-side unary interceptors.
UnaryInterceptors []grpc.UnaryClientInterceptor
}
GRPCClientConfig holds dial-time settings for a gRPC client connection.
func (*GRPCClientConfig) WithDefaults ¶
func (c *GRPCClientConfig) WithDefaults(targetName string) *GRPCClientConfig
WithDefaults fills zero keepalive fields with defaults and returns a new options value.
type GRPCClientConn ¶
type GRPCClientConn struct {
// contains filtered or unexported fields
}
GRPCClientConn is an active connection to a gRPC server.
func NewGRPCClientConn ¶
func NewGRPCClientConn(target GRPCConnTarget, config *GRPCClientConfig) (*GRPCClientConn, error)
NewGRPCClientConn creates a new gRPC client connection. The connection is established using insecure credentials. Since all these connections are within the k8s cluster and not over a public network, this is acceptable.
Example ¶
ExampleNewGRPCClientConn shows the minimal configuration for dialing another service: only the target is required; a nil config receives production defaults.
package main
import (
"github.com/open-mrp/api/shared/contracts"
)
func main() {
conn, err := contracts.NewGRPCClientConn(contracts.GRPCConnTarget{
URL: "auth-service:9092",
Name: "auth-service",
}, nil)
if err != nil {
panic(err)
}
defer conn.Close()
}
Output:
func (*GRPCClientConn) Close ¶
func (c *GRPCClientConn) Close() error
Close closes the gRPC client connection.
func (*GRPCClientConn) Conn ¶
func (c *GRPCClientConn) Conn() *grpc.ClientConn
Conn returns the underlying gRPC client connection.
func (*GRPCClientConn) WaitForReady ¶
func (c *GRPCClientConn) WaitForReady(ctx context.Context) error
WaitForReady waits for the gRPC server to be ready. If an error occurs, it is returned. If the context is canceled, the context error is returned. Otherwise, we return when the SERVING status is returned.
type GRPCConnTarget ¶
type GRPCConnTarget struct {
// URL (required) is the dial address (e.g. "auth-service:9092").
URL string
// Name (required) is a human-readable identifier used in logs and error messages.
Name string
}
GRPCConnTarget identifies the gRPC server to connect to.
type GRPCServer ¶
type GRPCServer struct {
// contains filtered or unexported fields
}
GRPCServer wraps a grpc.Server and its health server.
func NewGRPCServer ¶
func NewGRPCServer(serverName string, logger *slog.Logger, config *GRPCServerConfig) (*GRPCServer, error)
NewGRPCServer creates a ready-to-use gRPC server with tracing, keepalive, enforcement policy, interceptor chain, and health checking pre-configured.
func (*GRPCServer) Serve ¶
func (s *GRPCServer) Serve(ctx context.Context, port int) error
Serve listens on the given port and blocks until ctx is cancelled or a fatal serve error occurs. On cancellation it attempts a graceful stop with a timeout before forcing an immediate stop.
func (*GRPCServer) Server ¶
func (s *GRPCServer) Server() *grpc.Server
Server returns the underlying grpc.Server for service handler registration.
type GRPCServerConfig ¶
type GRPCServerConfig struct {
// KeepaliveParams (optional; default: 15m idle, 30m age, 5s grace, 30s ping, 5s timeout) controls how the server manages idle connections, connection age limits, and server-side ping behavior.
KeepaliveParams keepalive.ServerParameters
// EnforcementPolicy (optional; default: 10s min ping time, permit without stream) controls the minimum time between client pings and whether pings are allowed when there are no active streams. PermitWithoutStream is always set to true by WithDefaults and cannot be disabled via this config.
EnforcementPolicy keepalive.EnforcementPolicy
// UnaryInterceptors (optional; default: SpanRenamer, Recovery, Identity, IdempotencyKey, RequestID, ClientIP, CanonicalLog) is the chain of server-side unary interceptors.
UnaryInterceptors []grpc.UnaryServerInterceptor
}
GRPCServerConfig holds settings for a gRPC server.
func (*GRPCServerConfig) WithDefaults ¶
func (c *GRPCServerConfig) WithDefaults(logger *slog.Logger) *GRPCServerConfig
WithDefaults fills zero-value fields with production defaults and returns a new config. When logger is nil, a default text handler logger writing to stdout is used.
type RunCompleteData ¶
type RunCompleteData struct {
AgentRunID string `json:"agent_run_id"`
AccountID string `json:"account_id"`
}
RunCompleteData is the WSTypeRunComplete payload sent to clients when an agent run finishes. It carries only what the live run view needs to leave its loading state and re-fetch authoritative status. Token usage and model metadata are deliberately excluded — they are never exposed to the frontend.
type WSMessage ¶
type WSMessage struct {
// Type identifies the message kind (e.g. "ping", "subscription_update").
Type string `json:"type"`
// Data is the message payload, serialized as JSON on the wire.
Data any `json:"data"`
}
WSMessage is a typed WebSocket message with an already-unmarshalled Data payload.