Help us shape the future of Go! Take the 2026 Go Developer Survey

controlevent

package
v0.2.49 Latest Latest
Warning

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

Go to latest
Published: Oct 20, 2026 License: MPL-2.0 Imports: 3 Imported by: 0

Documentation

Overview

Package controlevent defines the control-plane event contract: the notification a control plane appends and a fleet chassis consumes.

See internal docs/todo-architecture-saas-fleet.md §3.1. An event is a semantic invalidation/version record — NEVER the payload itself. It carries identity, a content-addressed pointer (ArtifactRef), an integrity seal (Checksum) and the fleet position (ControlVersion). The bytes live in the artifact store, not in the log.

This package is the shared event contract: the chassis consumes events; a control plane produces them. It is intentionally dependency-free so both sides can depend on it.

Index

Constants

View Source
const (
	// ControlModelVersion is the version of the event/contract shape.
	ControlModelVersion = 1
	// CacheSchemaVersion is the version of the materialised local cache
	// shape an artifact restores into.
	CacheSchemaVersion = 1
)

Model/cache versions. A node rejects an artifact (or event) whose declared versions it cannot interpret (see snapshot.Verify). Bump deliberately when the on-the-wire contract or the materialised cache shape changes incompatibly.

View Source
const (
	TypeStackActivated     = "stack.activated"
	TypeTenantCreated      = "tenant.created"
	TypeHostnameBound      = "hostname.bound"
	TypeHostnameVerified   = "hostname.verified"
	TypeHostnameRevoked    = "hostname.revoked"
	TypeActorChanged       = "actor.changed"
	TypeKeyChanged         = "key.changed"
	TypeMembershipChanged  = "membership.changed"
	TypeEntitlementUpdated = "entitlement.updated"
	TypeSystemOpstack      = "system.opstack.updated"
	// TypeDNSZoneUpserted carries a dns_zones row (RowsArtifact, op=upsert;
	// revocation is an upsert with revoked_at set). Lets a data-plane node hold
	// the delegated-zone state so it re-derives `<label>.<origin>` routing hosts
	// itself and (with the dns personality) can serve the zone — see
	// internal docs/todo-dns-authority.md §9 fleet note.
	TypeDNSZoneUpserted = "dns.zone.upserted"
	// TypeDNSRecordUpserted carries a dns_records row (RowsArtifact, op=upsert;
	// revocation is an upsert with revoked_at set, mirroring dns.zone.upserted).
	// Without it an override-record write is admin-local: a SQLite data-plane
	// node only sees dns_records via snapshot re-bootstrap, and a shared-PG node
	// only on the next unrelated event's mirror reload — so a dns head can serve
	// a zone whose records lag the control plane indefinitely.
	TypeDNSRecordUpserted = "dns.record.upserted"
	// TypeCronSettingsUpserted carries a cron_settings row (RowsArtifact,
	// op=upsert; clearing a timezone is an upsert with timezone=”). Lets every
	// node localize a tenant's @cron.* wall-clock fields consistently.
	TypeCronSettingsUpserted = "cron.settings.upserted"
	// TypeDNSSettingsUpserted carries the singleton dns_settings row
	// (RowsArtifact, op=upsert). Chassis-global synthesis config (NS/edge/MX/
	// TTL): without it a `dns config set` was admin-local and every dns head
	// kept synthesizing from its own boot flags until an unrelated event
	// nudged a reload — the same silent gap dns.record.upserted closed.
	TypeDNSSettingsUpserted = "dns.settings.upserted"
	// TypeSecretChanged carries a tenant_secrets OR tenant_secret_versions row
	// (RowsArtifact, op=upsert). A create/rotate emits two of these — the
	// version row first, then the parent — so a consumer never briefly sees a
	// parent pointing at an absent version. Decryptable fleet-wide only when the
	// master key is shared (TXCO_SECRET_MASTER_KEY_B64). The blob columns travel
	// base64-wrapped ({"$b64":…}); see controlapply.coerce.
	TypeSecretChanged = "secret.changed"
	// TypeSecretRevoked carries a tenant_secrets row with revoked_at set
	// (RowsArtifact, op=upsert — the consumer's INSERT OR REPLACE flips it
	// inactive, mirroring dns.zone revocation). No version row travels.
	TypeSecretRevoked = "secret.revoked"
	// TypeRunAbort carries a RunAbortArtifact: end a run in flight — one, by
	// rid, or every run of a stack — wherever it is. The admin plane cannot
	// know which node holds a run (nodes do not report their live runs), so
	// every node applies the event to its own live-run registry; on all but
	// one it ends nothing, which is the expected outcome, not an error. Not
	// a row: nothing is stored, and a node that first sees the event after
	// the run ended has nothing to do.
	TypeRunAbort = "run.abort"
)

Event types. These are the existing admin-API mutations, externalised.

Variables

View Source
var ErrInvalid = errors.New("controlevent: invalid event")

ErrInvalid wraps every Validate failure.

Functions

func CompatibleCacheSchema

func CompatibleCacheSchema(cacheSchemaVersion int) bool

CompatibleCacheSchema reports whether this chassis can load an artifact declaring the given cache-schema version.

func CompatibleModel

func CompatibleModel(modelVersion int) bool

CompatibleModel reports whether this chassis can interpret an artifact/event declaring the given control-model version. Same major line only; an older binary must refuse a newer model rather than guess.

Types

type Event

type Event struct {
	ID             int64  `json:"id"`
	EventID        string `json:"event_id"`
	Type           string `json:"type"`
	TenantID       string `json:"tenant_id,omitempty"`
	StackID        string `json:"stack_id,omitempty"`
	Version        int64  `json:"version,omitempty"`
	BaseVersion    int64  `json:"base_version,omitempty"`
	ArtifactRef    string `json:"artifact_ref,omitempty"`
	Checksum       string `json:"checksum,omitempty"`
	ControlVersion uint64 `json:"control_version"`
	CreatedAt      string `json:"created_at,omitempty"`
}

Event is the control-plane event contract.

Field notes (deliberate, see plan):

  • EventID is the producer-assigned semantic identity (UUID; UUIDv7 via chassis/hxid is the conventional choice). It rides the event through retries and replays so consumers can recognise a duplicate even when broker-side dedup (e.g. JetStream Nats-Msg-Id) has expired. Required; an empty EventID fails Validate.
  • Version is the *stack* version (stack_versions.version_number).
  • BaseVersion is the producer's view of the prior value of the mutated resource — e.g. the active_version it saw before flipping it for a stack.activated event. Optional in v1; not enforced by the applier. Carried so a later revision can layer CAS-style optimistic concurrency on top without a wire-format change.
  • ControlVersion is the global monotonic *fleet cursor* — the resume position. Producer-side it is zero (the backend's broker stamps it on publish — JetStream stream_sequence in the NATS overlay; sequential ints in the file backend). Consumer-side Validate requires > 0.
  • ArtifactRef points at the event's artifact in the artifact store. It is "the artifact for THIS event", not necessarily a full bootstrap SQLite snapshot — hence ArtifactRef, not "snapshot_ref".
  • There is no "active_version": it was ambiguous against Version and is omitted until/unless it earns a distinct meaning.

func (Event) Validate

func (e Event) Validate() error

Validate checks the structural invariants every consumer relies on. It is deliberately strict: a malformed system event must fail loudly, never silently misroute.

Validate is consumer-side: it runs after the carrier has stamped ControlVersion (broker-assigned). Producers do not need to call it before queueing an outbox row — the Sink stamps ControlVersion at publish time, and the next applier on any chassis re-runs Validate.

type RowsArtifact

type RowsArtifact struct {
	DB    string           `json:"db"`    // "runtime" | "auth"
	Table string           `json:"table"` // must be whitelisted
	Op    string           `json:"op"`    // "upsert" | "delete"
	PK    []string         `json:"pk"`    // primary-key columns (delete)
	Rows  []map[string]any `json:"rows"`
}

RowsArtifact is the payload for the generic row event types (tenant.created, hostname.*, membership.changed, entitlement.updated, and the auth.* types). It carries authoritative rows for one table and how to apply them. DB selects the target database ("runtime" or "auth"); the table is checked against a hard whitelist on the consumer side before any SQL runs.

type RunAbortArtifact added in v0.2.38

type RunAbortArtifact struct {
	Tenant string `json:"tenant"`
	RID    string `json:"rid,omitempty"`
	Stack  string `json:"stack,omitempty"`
	By     string `json:"by"`
	Reason string `json:"reason,omitempty"`
	At     string `json:"at"`
}

RunAbortArtifact is the payload of a run.abort event: the run to end (RID), or every live run of a stack (Stack; one of the two), in the tenant named by its slug — the live-run registry keys on the slug the processor pins — and who asked and why, for the run's trace.

type StackActivatedArtifact

type StackActivatedArtifact struct {
	TenantID string              `json:"tenant_id"`
	Stack    string              `json:"stack"`
	Version  int64               `json:"version"`
	Files    []StackArtifactFile `json:"files"`
}

StackActivatedArtifact is the payload for a TypeStackActivated event: the full file set of the activated stack version. The node upserts these into stack_versions/stack_files then materialises them into ops.

type StackArtifactFile

type StackArtifactFile struct {
	Path        string `json:"path"`
	Content     string `json:"content,omitempty"`
	ContentHash string `json:"content_hash,omitempty"`
}

StackArtifactFile is one file of a StackActivatedArtifact. Path is "<scope>/<name>.txcl", the well-known "mock-request.json" / "mock-response.json", or a "FILES/**" static asset.

Rule/fixture files carry their bytes inline in Content. FILES/** static assets instead carry only ContentHash (the sha256 fingerprint): their bytes live in the shared content-addressed store (filecas), so the event stays small and data-plane nodes never inline tenant file bytes into the in-memory runtime DB. Both fields are omitempty so old (all-inline) and new (fingerprint) events parse in either direction.

Jump to

Keyboard shortcuts

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