condition

package
v0.9.1 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: Apache-2.0 Imports: 10 Imported by: 0

Documentation

Overview

Package condition is the vocabulary an expression may use, and the shape of what it is told.

Two engines evaluate conditions - the rule engine and the retention sweep - and they read the same language (ADR-0009, automation.md §1.2, data-retention.md §2). This package is what makes that one statement: the names, and the projection of an entry into the document an expression reads. Two copies would be two dialects, and the day they diverged nobody would find out from a test.

The projection is deliberately narrow. Every field named here becomes part of what a rule written today may depend on, so adding one is a promise and removing one is a breaking change - which is why this is a written list rather than a reflection over the aggregate.

Index

Constants

View Source
const (
	VarEvent      = "event"
	VarItem       = "item"
	VarParent     = "parent"
	VarCollection = "collection"
	VarHub        = "hub"
	VarActor      = "actor"
	VarNow        = "now"
	VarPayload    = "payload"
	VarTenant     = "tenant"
	// VarData is the CloudEvent's `data` - the event's payload - as a webhook subscriber receives
	// it. Only a webhook subscription's condition names it.
	VarData = "data"
)

The names an expression may use. Written out rather than derived, because they are a contract: automation.md §1.2 lists them, and an expression naming anything else fails when it is written.

Variables

This section is empty.

Functions

func CollectionOf

func CollectionOf(envelope event.Envelope) shared.ID

CollectionOf reads the collection out of an event's payload.

Two shapes, because there are two kinds of event. An item event names the collection it is in; a container event *is* the container, so a collection names itself and a hub names nothing - a hub-scoped rule matches the hub's own events through the hub identity below.

func HubOf

func HubOf(envelope event.Envelope) shared.ID

HubOf reads a hub the event is directly about, which is the case a collection lookup cannot cover.

func ItemDocument

func ItemDocument(item work.WorkItem) map[string]any

ItemDocument is one entry as an expression reads it.

Snake case, because that is what the API answers and what somebody writing a condition has in front of them - `item.completed_at`, not `item.completedAt`. The names match the contract rather than the Go field, which is the whole reason this is a written projection.

Absent optional values are absent keys rather than nulls, so that `has(item.due_at)` answers what it looks like it answers. A null would make `item.due_at == null` and `!has(item.due_at)` two different questions with one answer.

func ItemOf

func ItemOf(envelope event.Envelope) shared.ID

itemOf reads the entry an event is about out of its subject.

The subject rather than the payload, because `<entity>/<id>` is the CloudEvents field that names what the event is about and every item event sets it. A payload key would be a second place to look, and the two would eventually disagree.

func JumbleEntryOf

func JumbleEntryOf(envelope event.Envelope) shared.ID

JumbleEntryOf reads the entry a jumble event is about out of its subject, ItemOf's shape.

func LabelledItemDocument

func LabelledItemDocument(item work.WorkItem, labels []string, members []string) map[string]any

LabelledItemDocument is ItemDocument with the sets an expression is most likely to ask about.

Separate, because the labels and members of an entry are not on the aggregate - they are read beside it - and a projection that pretended otherwise would answer an empty list for an entry whose labels nobody had loaded. An empty list and "not loaded" are the same value and different facts, and a condition on the wrong one of them is silently false.

func PlaceOf

func PlaceOf(envelope event.Envelope) []identity.Scope

PlaceOf is where an event happened as the authoriser reads a path: the workspace, then the hub, collection or entry the event names. The authoriser completes a collection with its hub and reads an entry's hub from storage, so the payload's own identifiers are enough to ask whether somebody reaches the place - a private hub's events are kept from whoever does not (ADR-0073 §1). An event about nothing in a hub answers the workspace alone.

func Placed

func Placed(path []identity.Scope) bool

Placed reports whether the place names anything below the workspace.

func RetentionEnvironment

func RetentionEnvironment() port.Environment

RetentionEnvironment is what a retention rule's condition may name (data-retention.md §2).

A smaller set of the same names, and smaller on purpose rather than by omission. A retention pass is deciding about one entry against the clock; there is no event, no actor and no payload, because nobody caused it - the clock did. Declaring them anyway would let somebody write `actor.id == …` into a retention rule and get a condition that is never true.

func RuleEnvironment

func RuleEnvironment() port.Environment

RuleEnvironment is what an automation rule's condition may name (automation.md §1.2).

`tenant` is a document rather than a name of its own, because the document writes `tenant.settings` - a variable called `tenant.settings` is not a name CEL can declare, and the settings are one field of the workspace rather than the whole of what a rule may know about it.

func WebhookEnvironment

func WebhookEnvironment() port.Environment

WebhookEnvironment is what a webhook subscription's condition may name (automation.md §3.1).

Only what the dispatcher holds: the event, its `data` exactly as the subscriber will receive it, the actor and the moment. Not the entry, its collection or hub - the dispatcher runs in the integration context and does not read work management, and a condition that could name them would be answered by an absent document that nobody noticed was absent.

Types

type Containers

type Containers interface {
	Find(ctx context.Context, id shared.ID) (work.Container, error)
}

Containers is the read `collection` and `hub` need: a container by its identifier. Narrow rather than a repository, so that nothing holding an activation can write through it.

type Entries

type Entries interface {
	Find(ctx context.Context, id shared.ID) (work.WorkItem, error)
}

Entries is the read an expression's `item` needs. Narrow for Containers' reason.

type JumbleEntries

type JumbleEntries interface {
	Find(ctx context.Context, id shared.ID) (jumble.Entry, error)
}

JumbleEntries is the read `payload` costs on a jumble run: the entry, by its identifier. Narrow for the reason every lookup here is.

type Level

type Level string

Level is how far down a scope reaches: the workspace, one hub, or one collection.

const (
	LevelTenant     Level = "TENANT"
	LevelHub        Level = "HUB"
	LevelCollection Level = "COLLECTION"
)

type Location

type Location struct {
	CollectionID shared.ID
	HubID        shared.ID
}

Location is the hub and collection an event happened in, as far as it can be said. An event about neither - a rule run, an arrival in the jumble - carries none, and nothing scoped below the workspace matches it: something scoped to one hub must not act on a workspace-wide event because nothing said otherwise. Shared by the rules and the webhook subscriptions, which scope alike (automation.md §1, §3.1).

func Locate

func Locate(ctx context.Context, containers Containers, envelope event.Envelope) (Location, error)

Locate reads the event's collection and the hub above it.

The collection comes from the payload, which every item event carries (`collection_id`) and every container event carries as its own identity. The hub is one read, and only when the event is in a collection at all.

func (Location) Path

func (l Location) Path() []identity.Scope

Path is the place as the authoriser reads it; a collection without its hub is completed there.

func (Location) Within

func (l Location) Within(level Level, id shared.ID) bool

Within reports whether a scope reaches this location.

The workspace reaches everything. A hub reaches its collections as well as itself, because a permission held at a hub applies downwards (domain-model.md §3.2) and a scope of a hub is a scope of what happens under it.

type Sets

type Sets interface {
	List(ctx context.Context, itemID shared.ID) ([]shared.ID, error)
}

Sets is the read `item.labels` and `item.members` need: the identifiers an entry carries, which live beside the aggregate rather than on it. Narrow, like Entries, so that nothing holding an activation can write through it; the label and the member repositories both fit.

type Values

type Values struct {
	Envelope event.Envelope
	// now is the run's single reading, passed rather than taken: an expression evaluated twice in
	// one run sees one instant (automation.md §1.2).
	Now time.Time
	// subject is the entry a run that no event started is about - a RELATIVE_DATE run measured
	// from one entry's due date. The envelope's subject wins where there is one, so an
	// event-triggered run is unaffected by this field existing.
	Subject shared.ID
	// payload is the body an inbound delivery carried. Untrusted from end to end: it is read as
	// *data* under one name and never rendered as an instruction to anything (ai-first.md §1.3,
	// automation.md §1.1).
	Payload    map[string]any
	Entries    Entries
	Containers Containers
	// Labels and Members answer the entry's two sets beside it, so that `item.labels` and
	// `item.members` are the lists automation.md §1 promises rather than absent keys.
	// Read only when `item` is - the same laziness - and optional: without them the document is
	// the entry alone.
	Labels  Sets
	Members Sets
	// JumbleID names the entry a JUMBLE_ENTRY run is about; `payload` is rendered from it, lazily
	// and as data. Zero everywhere else - the envelope's own subject still lets an EVENT
	// rule on a jumble event read the same names.
	JumbleID shared.ID
	Jumble   JumbleEntries
}

Values is what an expression is told about one event, resolved when it asks.

The laziness is the port's promise and the reason the engine can afford conditions at all: a condition naming only `event` costs no reads, and one naming `item` costs one. The engine evaluates every enabled rule against every event, so eagerly building `collection`, `hub` and `parent` would turn one event into four queries per rule (automation.md §1.2).

The lookups are optional. A caller that has none - the subscriber rendering a dedupe key inside the dispatcher's transaction, which may not read the work management context - answers `item` as absent rather than failing, and an expression that names it sees a value that is not there.

func (Values) EntryID

func (v Values) EntryID() shared.ID

EntryID is entry for a caller outside the expressions: the engine, which supplies the entry to the actions beside the event (automation.md §2.2), so that the actions and the conditions of one run cannot be about two different entries. Zero for a run that is about none - a container event, a jumble arrival, a schedule.

func (Values) Resolve

func (v Values) Resolve(ctx context.Context, name string) (any, bool, error)

Resolve answers one name.

Jump to

Keyboard shortcuts

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