Documentation
¶
Overview ¶
Package domain holds the shared value types and port interfaces that cross Portcullis package boundaries.
It is the leaf of the dependency graph: it imports only the standard library (and protocol SDK types) and nothing from other internal packages. Concrete packages depend on domain, never the other way around, which keeps the internal import graph acyclic by construction.
Index ¶
Constants ¶
const NamespaceSeparator = "__"
NamespaceSeparator joins a downstream name and a tool name into the single client-facing tool name the gateway presents (for example "github__create_issue").
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AuditRecord ¶
type AuditRecord struct {
// Timestamp is when the call completed.
Timestamp time.Time
// ClientID is the calling client's identity.
ClientID string
// ToolName is the namespaced tool name.
ToolName string
// Decision is the policy reason code recorded for the call.
Decision ReasonCode
// Allowed reports whether the call was permitted by policy.
Allowed bool
// LatencyMS is the end-to-end call latency in milliseconds.
LatencyMS int64
// BreakerState is the circuit-breaker state observed for the call.
BreakerState string
// Redactions counts the values redacted from the result, error, and
// notifications before this record was written.
Redactions int
// Truncated reports whether the result exceeded the size cap and was
// truncated before scanning.
Truncated bool
// Error is the redacted error message, empty when the call succeeded.
Error string
}
AuditRecord is the post-redaction record written for every call.
It is designed for reuse via a sync.Pool on the audit hot path (design §5): the volatile per-call fields are overwritten on each use, and Reset clears the record so a pooled instance can be reused without leaking the previous call's data. Long-lived string fields (ClientID, ToolName) are assigned by reference from configuration rather than copied.
func (*AuditRecord) Reset ¶
func (r *AuditRecord) Reset()
Reset clears every field so the record can be returned to a pool and reused without leaking data from a previous call.
type Call ¶
type Call struct {
// Client is the authenticated identity that issued the call.
Client Identity
// Tool is the resolved downstream tool being invoked.
Tool ToolRef
// Args is the raw JSON arguments object from the client request. Like
// Tool.InputSchema it is treated as immutable: stages that redact or inject
// must produce new bytes rather than mutating the underlying array in place,
// which would be visible to anything else holding the same Call.
Args json.RawMessage
}
Call is a single in-flight tool invocation as it moves through the security pipeline. It carries the data stages need; behavior (deciding, injecting, dispatching) is supplied by the injected port interfaces.
type Catalog ¶
type Catalog struct {
Tools []Tool
}
Catalog is the set of tools presented to a client, after namespacing and policy filtering.
func (Catalog) Lookup ¶
Lookup returns the tool with the given namespaced name and whether it exists.
The scan is linear by design: a per-client catalog holds only the tools that client may see (tens of entries), built once and queried per call, so a map would add allocation and upkeep for no real gain at this scale.
type CatalogFilter ¶
CatalogFilter reduces a full namespaced catalog to the subset a client is permitted to see.
Filtering is security-load-bearing: a client must never see the schema or description of a tool it may not call (design §8).
type Decider ¶
Decider evaluates whether a client may call a tool.
Implementations are deny-by-default and resolve conflicting rules most-restrictively: a deny beats an allow (design §7). The evaluation is a pure in-memory lookup, so it takes no context and never fails — the outcome is always a Decision carrying a reason code.
type Decision ¶
type Decision struct {
Allow bool
Reason ReasonCode
}
Decision is the outcome of evaluating a client's access to a tool. The zero value is a denial, so a default-constructed Decision fails closed; to express an explicit deny, use Denied with a reason rather than a bare zero value, so the outcome always carries a reason code (design §7).
func Denied ¶
func Denied(reason ReasonCode) Decision
Denied builds a deny decision carrying the given reason.
type Dispatcher ¶
Dispatcher routes a call to its owning downstream session and executes it. It is the base the pipeline terminates in; routing is owned by aggregation and execution by the registry.
type HandlerFunc ¶
HandlerFunc executes a call and returns its result. It is the unit the pipeline composes; the chain terminates in a HandlerFunc backed by a Dispatcher.
type Identity ¶
type Identity struct {
// ID is the configured client identifier (for example "claude-desktop").
ID string
}
Identity is an authenticated gateway client, resolved from its API key. Policy is evaluated against this identity.
type ReasonCode ¶
type ReasonCode string
ReasonCode is a stable, machine-readable code attached to a policy decision. Every decision carries one so outcomes are auditable and metered, and no outcome is ever silently dropped (design §7: "reason code + metric").
const ( // ReasonAllowed: an explicit allow rule matched the client and tool. ReasonAllowed ReasonCode = "allowed" // ReasonDeniedDefault: no rule allowed the tool, so deny-by-default applies. ReasonDeniedDefault ReasonCode = "denied_default" // ReasonDeniedExplicit: a deny rule matched, overriding any allow rule // (most-restrictive-wins). ReasonDeniedExplicit ReasonCode = "denied_explicit" )
type Result ¶
type Result struct {
// Content is the raw tool-call result payload (the MCP CallToolResult as
// JSON). Outbound redaction rewrites it; like Call.Args it is treated as
// immutable, so a stage that redacts produces new bytes.
Content json.RawMessage
// IsError reports whether the downstream returned a tool execution error.
IsError bool
}
Result is the outcome of a tool call as it flows back through the pipeline: from dispatch, through outbound redaction, to the client. The payload is kept as raw JSON so the redactor can scan and rewrite it without the pipeline needing to understand the downstream's content shape.
type Stage ¶
Stage is one element of the per-call security chain. It wraps the next handler à la net/http middleware: it may short-circuit before calling next (for example, a policy denial), or call next and post-process the result (for example, outbound redaction).
type Tool ¶
type Tool struct {
// Ref identifies the downstream and tool this entry routes to.
Ref ToolRef
// Title is the optional human-readable display name for the tool.
Title string
// Description is the human-readable description shown to the client.
Description string
// InputSchema is the tool's JSON Schema for its arguments.
InputSchema json.RawMessage
// OutputSchema is the optional JSON Schema for a structured result, or nil if
// the downstream advertised none. Clients validate structured output against it.
OutputSchema json.RawMessage
// Annotations is the tool's optional annotations (display and behavior hints)
// as raw JSON, or nil if none.
Annotations json.RawMessage
// Icons is the tool's optional icon set as raw JSON, or nil if none.
Icons json.RawMessage
}
Tool is a single callable tool as presented in the gateway's namespaced catalog, including the schema, description, and display metadata the client sees.
The metadata fields beyond Ref are carried verbatim from the downstream's listing so the client receives the tool exactly as the downstream advertised it. OutputSchema, Annotations, and Icons are kept as opaque raw JSON — the gateway never interprets them, and the domain layer stays free of any transport (MCP SDK) types. All of them are treated as immutable; callers must not mutate the underlying bytes in place.
type ToolRef ¶
type ToolRef struct {
// Downstream is the configured downstream server name (for example "github").
Downstream string
// Tool is the tool's own name on that downstream (for example "create_issue").
Tool string
}
ToolRef identifies a tool on a specific downstream server.
func ParseToolRef ¶
ParseToolRef splits a client-facing namespaced tool name back into its downstream and tool components.
The name must contain exactly one separator, with a non-empty downstream and a non-empty tool. Any other shape — no separator, an empty side, or a name whose downstream or tool itself contains the separator — is rejected, so a malformed or ambiguous name can never be silently mis-routed (design §7).
func (ToolRef) Namespaced ¶
Namespaced returns the client-facing namespaced tool name.