protocol

package
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package protocol is the ObserverLoop wire contract: the signed envelope, the identifier grammar, the NATS subject grammar, the JetStream topology, and the JSON Schema payload set.

It has no dependency on any ObserverLoop implementation repository. Every enumeration in this package is generated from registry.yaml at the root of the containing repository; the only hand-written enumeration is that file.

Index

Constants

View Source
const (
	ProtocolVersion = "1.3"
	ProtocolMajor   = 1
	ProtocolMinor   = 3
)

The protocol version this binding implements, from the registry. The subject root carries MAJOR only: an unknown MAJOR is rejected, an unknown MINOR is accepted.

View Source
const IdentifierMaxLength = 63

IdentifierMaxLength bounds every identifier. It is chosen so that a canonical lowercase UUIDv7 text form fits unchanged.

View Source
const SubjectMaxLength = 255

SubjectMaxLength is the NATS subject limit. The builder enforces it and returns an error rather than publishing a truncated subject.

Variables

View Source
var (
	// ErrBadIdentifier is returned when a token does not satisfy the identifier
	// grammar, or when a subject template requires a token that is empty.
	ErrBadIdentifier = errors.New("protocol: identifier does not satisfy the grammar")

	// ErrUnknownEventType is returned when a type token is in no registry entry,
	// and when a subject matches no event type's template.
	ErrUnknownEventType = errors.New("protocol: unknown event type")

	// ErrSubjectTooLong is returned when a built subject would exceed the
	// 255-byte NATS limit. The subject builder never truncates.
	ErrSubjectTooLong = errors.New("protocol: subject exceeds the 255-byte limit")

	// ErrSchemaViolation is returned when an envelope or its payload fails the
	// schema registered for it.
	ErrSchemaViolation = errors.New("protocol: schema violation")

	// ErrUnsupportedMajor is returned for an unknown protocol MAJOR version. An
	// unknown MINOR is accepted, never reported.
	ErrUnsupportedMajor = errors.New("protocol: unsupported protocol major version")
)

The sentinel errors a caller must be able to branch on. Every failure this package reports wraps exactly one of them, so `errors.Is` is sufficient and no caller ever needs to match an error string.

Functions

func Canonicalize

func Canonicalize(raw []byte) ([]byte, error)

Canonicalize returns the RFC 8785 JSON Canonicalization Scheme form of raw.

This is the only place canonicalisation happens. Signing, verification, and digesting all route through it, so there is no way for two components to disagree about which bytes were signed.

func CheckProtocolVersion

func CheckProtocolVersion(s string) error

CheckProtocolVersion applies the compatibility rule: an unknown MAJOR is rejected because the subject root carries MAJOR and a MAJOR bump is an enrollment-level operation, while an unknown MINOR is accepted because unknown optional fields are ignored and preserved.

func ParseProtocolVersion

func ParseProtocolVersion(s string) (major, minor int, err error)

ParseProtocolVersion splits a MAJOR.MINOR version token.

func ParseSubject

func ParseSubject(subject string) (EventType, SubjectParams, error)

ParseSubject is the inverse of SubjectFor. Round-tripping is asserted by a table test over every event type.

It returns ErrUnknownEventType when no template and type token combination matches, which is also the answer for a malformed subject: the protocol has no way to name what such a subject would be.

func SchemaFS

func SchemaFS() fs.FS

SchemaFS exposes the embedded schema tree so that a consumer can serve or re-validate the published schemas without vendoring them. Paths are rooted at "schemas", matching the paths SchemaPathFor returns.

func SchemaPathFor

func SchemaPathFor(t EventType) (string, error)

SchemaPathFor returns the payload schema path registered for t, relative to the repository root and to the embedded schema filesystem alike.

func SubjectFor

func SubjectFor(t EventType, p SubjectParams) (string, error)

SubjectFor builds the subject for t. It returns an error when t is unknown, when a token the template requires is empty or malformed, or when the result would exceed SubjectMaxLength. It never truncates.

Types

type Capability

type Capability string

Capability is a token a signer must hold, resolved through its delegation chain, before an event type may be accepted from it.

const (
	CapabilityAgentLifecycle     Capability = "agent.lifecycle"
	CapabilityAuditWrite         Capability = "audit.write"
	CapabilityCommandExecute     Capability = "command.execute"
	CapabilityCommandIssue       Capability = "command.issue"
	CapabilityInteractionRequest Capability = "interaction.request"
	CapabilityInteractionResolve Capability = "interaction.resolve"
	CapabilityThreadWrite        Capability = "thread.write"
)

func CapabilitiesFor

func CapabilitiesFor(t EventType) []Capability

CapabilitiesFor returns the capabilities a signer must hold for t, or nil when t is unknown. The caller receives a copy.

type Discard

type Discard string

Discard is a JetStream discard policy: which message is dropped when a limit is reached.

const (
	DiscardOld Discard = "old"
)

type Durability

type Durability string

Durability selects the transport guarantee for an event type.

const (
	// DurabilityJetStream events are published to a JetStream stream and are
	// redelivered until acknowledged.
	DurabilityJetStream Durability = "jetstream"
	// DurabilityCore events are published to core NATS with no stream behind
	// them; they are request-scoped and are not replayed.
	DurabilityCore Durability = "core"
)

func DurabilityFor

func DurabilityFor(t EventType) Durability

DurabilityFor returns the transport guarantee registered for t. An unknown type has no registered durability and returns the empty Durability, which is never a publishable value.

type Envelope

type Envelope struct {
	ProtocolVersion        string          `json:"protocol_version"`
	EventID                Identifier      `json:"event_id"`
	EventType              EventType       `json:"event_type"`
	TrustDomain            TrustDomain     `json:"trust_domain"`
	TenantID               Identifier      `json:"tenant_id"`
	WorkspaceID            Identifier      `json:"workspace_id"`
	ThreadID               *Identifier     `json:"thread_id,omitempty"`
	ActivityID             *Identifier     `json:"activity_id,omitempty"`
	SourceConductorID      *Identifier     `json:"source_conductor_id,omitempty"`
	ConductorEpoch         *int64          `json:"conductor_epoch,omitempty"`
	SourceAgentID          *Identifier     `json:"source_agent_id,omitempty"`
	DestinationConductorID *Identifier     `json:"destination_conductor_id,omitempty"`
	DestinationAgentID     *Identifier     `json:"destination_agent_id,omitempty"`
	DefinitionDigest       *string         `json:"definition_digest,omitempty"`
	OccurredAt             time.Time       `json:"occurred_at"`
	Sequence               int64           `json:"sequence"`
	CorrelationID          Identifier      `json:"correlation_id"`
	CausationID            *Identifier     `json:"causation_id,omitempty"`
	ExpiresAt              *time.Time      `json:"expires_at,omitempty"`
	Payload                json.RawMessage `json:"payload"`
	SignerKeyID            string          `json:"signer_key_id"`
	DelegationChain        []string        `json:"delegation_chain"`
	Signature              string          `json:"signature"`
	// contains filtered or unexported fields
}

Envelope is the signed unit of the protocol. Field order matches the specification; the wire encoding is key-sorted, because RFC 8785 sorts.

func Unmarshal

func Unmarshal(data []byte) (*Envelope, error)

Unmarshal decodes a wire envelope. It does not validate: call Validate.

func (*Envelope) Marshal

func (e *Envelope) Marshal() ([]byte, error)

Marshal encodes the envelope for the wire.

func (Envelope) MarshalJSON

func (e Envelope) MarshalJSON() ([]byte, error)

MarshalJSON encodes the envelope with timestamps normalised to UTC at millisecond precision. Every other field is encoded from its struct tag, so the field set is declared exactly once.

func (*Envelope) SigningBytes

func (e *Envelope) SigningBytes() ([]byte, error)

SigningBytes returns the RFC 8785 canonical form of the envelope with signature omitted. This is the exact byte sequence that is signed and verified; nothing else may be signed.

func (*Envelope) UnmarshalJSON

func (e *Envelope) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes a wire envelope, keeps any field this binding does not know about, and rejects a timestamp that is not exactly RFC 3339 UTC at millisecond precision. The strictness is deliberate: Go's time decoder would happily accept a coarser or finer form, re-encode it in the canonical one, and produce signing bytes that differ from the bytes the signer signed.

func (*Envelope) Validate

func (e *Envelope) Validate() error

Validate checks the envelope against the embedded envelope schema and the payload against the schema registered for EventType. It also applies the two rules the schemas deliberately do not restate: the protocol MAJOR must be supported, and every identifier must satisfy the grammar - the schemas carry only the length bound, because ParseIdentifier is the single validator.

It does not verify the signature. That is the verifier's job, and it needs a key store this package must not depend on.

type EventType

type EventType string

EventType is a registry type token. It is simultaneously the envelope event_type, the trailing token of the subject, and the payload schema basename, so the three can never drift.

const (
	EventAgentLifecycle      EventType = "agent.lifecycle"
	EventAuditIncident       EventType = "audit.incident"
	EventAuditSchema         EventType = "audit.schema"
	EventAuditSecurity       EventType = "audit.security"
	EventAuditSignature      EventType = "audit.signature"
	EventChatMessage         EventType = "chat.message"
	EventCommandRequested    EventType = "command.requested"
	EventCommandResult       EventType = "command.result"
	EventDecisionRecord      EventType = "decision.record"
	EventPermissionRequested EventType = "permission.requested"
	EventPermissionResolved  EventType = "permission.resolved"
	EventQuestionRequested   EventType = "question.requested"
	EventQuestionResolved    EventType = "question.resolved"
	EventToolCall            EventType = "tool.call"
	EventToolResult          EventType = "tool.result"
)

func AllEventTypes

func AllEventTypes() []EventType

AllEventTypes returns every registry type token, sorted. The caller receives a copy: the generated table is not exposed to mutation.

func ParseEventType

func ParseEventType(s string) (EventType, bool)

ParseEventType reports whether s is a registry type token, and returns it. It is a lookup, not a parse: the permitted set is generated from the registry and is closed.

func (EventType) String

func (t EventType) String() string

String returns the type token.

type Identifier

type Identifier string

Identifier is a validated, NATS-subject-safe token. The zero value is not a valid identifier; construct one only through ParseIdentifier.

func ParseIdentifier

func ParseIdentifier(s string) (Identifier, error)

ParseIdentifier is the single validation point for every identifier in the system. It returns ErrBadIdentifier for anything that does not match ^[a-z0-9-]{1,63}$ - including the empty string, uppercase, dots, NATS wildcards, whitespace, and anything longer than 63 bytes.

func (Identifier) String

func (i Identifier) String() string

String returns the token. It exists so an Identifier can be used where a fmt.Stringer is expected without an explicit conversion.

type Retention

type Retention string

Retention is a JetStream retention policy.

const (
	RetentionLimits    Retention = "limits"
	RetentionWorkQueue Retention = "workqueue"
)

type StreamSpec

type StreamSpec struct {
	Name            string
	Subjects        []string
	Retention       Retention
	MaxAge          time.Duration
	Discard         Discard
	Replicas        int
	DuplicateWindow time.Duration
}

StreamSpec is a JetStream stream, resolved for one tenant and workspace. It is deliberately a plain description rather than a broker call: this module must not depend on a NATS client.

func StreamsFor

func StreamsFor(tenant, workspace Identifier, replicas int) []StreamSpec

StreamsFor resolves every stream template for one workspace. The subject bindings are generated from the subject grammar, so a stream can never listen on a subject the grammar does not produce.

The identifiers are substituted as given: they are already validated by the time a caller holds an Identifier, and this function has no error to return per the published surface.

type SubjectParams

type SubjectParams struct {
	TenantID    Identifier
	WorkspaceID Identifier
	ThreadID    Identifier
	AgentID     Identifier
	ConductorID Identifier
	CommandID   Identifier
}

SubjectParams carries every token a subject template can interpolate. Templates ignore the fields they do not use.

type TrustDomain

type TrustDomain string

TrustDomain names the class of key that signed an envelope. Every envelope declares exactly one.

const (
	TrustDomainSaaSAttested TrustDomain = "saas-attested"
	TrustDomainTenant       TrustDomain = "tenant"
)

func TrustDomainsFor

func TrustDomainsFor(t EventType) []TrustDomain

TrustDomainsFor returns the trust domains permitted to sign t, or nil when t is unknown. The caller receives a copy.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL