Documentation
¶
Overview ¶
Package model holds the kind-agnostic CORE contract: the kind-identity primitives (Kind, KindVersion, ResourceRef, ResourceManifest), the reaction request/response and composition value types the control plane builds from a DB row and applies (Resource, ReactionRequest, Outcome, ChildSpec, DepEdge, …), the author-facing Trigger/Transition enums, the config-merge helpers, the per-stage wire DTOs, the CRD-style KindManifest with its validation lattice, and the StageDispatcher seam. It is the CORE-SIDE twin of the worker SDK's own types (sdk-go/converge) — both describe the same worker proto, neither imports the other, and the two meet ONLY at the proto (the broker's internal/wire converter ⇄ the SDK's convert.go). It imports no other private package and touches no database.
It is INTERNAL: a worker author does NOT import it — they implement converge.Provider and speak converge.Resource/Outcome/ReactionRequest (sdk-go/converge). This package is what the ENGINE speaks on its side of the wire; a change here that alters the wire shape must be mirrored in the SDK's copy + the proto.
Concrete per-kind Spec/Status types live next to each provider. A kind comes online when its KindManifest is applied to kind_manifest (operator- or boot-seeded); this package does not enumerate them.
Index ¶
- Constants
- func IsTerminal(err error) bool
- func Terminal(err error) error
- func ValidateManifest(m KindManifest) error
- type ChildSpec
- type ComposePipelineResult
- type ComposeRequest
- type Condition
- type ConditionStatus
- type DeleteRequest
- type DepEdge
- type Env
- type Kind
- type KindManifest
- func (m KindManifest) ComposerReaction() (ReactionDecl, bool)
- func (m KindManifest) DeleteReaction() (ReactionDecl, bool)
- func (m KindManifest) IsReactor() bool
- func (m KindManifest) OperationReaction(verb string) (ReactionDecl, bool)
- func (m KindManifest) ReactorReaction() (ReactionDecl, bool)
- func (m KindManifest) RollupReaction() (ReactionDecl, bool)
- func (m KindManifest) WorkerReaction() (ReactionDecl, bool)
- type KindVersion
- type OperateRequest
- type Operation
- type Outcome
- type OutcomeBit
- type OutcomeMask
- type ProviderConfigSpec
- type ReactRequest
- type ReactionDecl
- type ReactionRequest
- type Resource
- type ResourceManifest
- type ResourceRef
- type RollupRequest
- type StageDispatcher
- type Transition
- type Trigger
- type ValueFlow
- type WorkRequest
Constants ¶
const ( // TypeReady is the health/availability axis (Crossplane "Ready"). // A reaction that returns TypeReady=False sets the resource's // health_ok=false, demoting is_ready even when fully synced. A // reaction that emits no Ready condition is treated as healthy // (health_ok stays true) — the common case. TypeReady = "Ready" // TypeSynced is the spec-reconciled axis (Crossplane "Synced"). It // is DERIVED from synced_gen vs generation and synthesized by the // API; providers do not normally emit it. The core writes a // Synced=False row on reconcile failure so the failure reason is // queryable as current truth. TypeSynced = "Synced" )
Standard condition types. Providers may also emit their own custom axes (e.g. "Bound", "Healthy"); only TypeReady feeds the health_ok scalar that gates is_ready.
Variables ¶
This section is empty.
Functions ¶
func IsTerminal ¶
IsTerminal reports whether err (or anything it wraps) was marked terminal via Terminal(). Used by the dispatcher's failure path.
func Terminal ¶
Terminal wraps err to signal a non-retryable (terminal) failure. The dispatcher records phase=Failed with failure_terminal=true so the scheduler stops re-queuing it. nil in → nil out.
func ValidateManifest ¶
func ValidateManifest(m KindManifest) error
ValidateManifest enforces the CLOSED legality lattice on a manifest's reactions — the SDK half of the DB validate_kind_manifest() CHECK; both must agree. The reaction rules reject a mis-declared manifest at AUTHORING/APPLY time, before a task ever runs against bad data. Returns a descriptive error on the first violation, nil if legal.
Types ¶
type ChildSpec ¶
type ChildSpec struct {
Kind Kind
// KindVersion is the web-API version the child is emitted at. A versioned composer
// (classicbom/v2) emits its children at the new kindVersion; because the (kind,
// name) address is stable, ApplyComposeResult ADOPTS the existing child BY ID
// and flips its kindVersion in place — a safe in-place upgrade, never a
// delete-and-recreate of live infra. REQUIRED and explicit (>= 1): there is no
// implicit v1 default — a composer MUST emit each child with the version it
// wants (a 0 fails the DB NOT-NULL/CHECK at ApplyComposeResult, never silently v1).
KindVersion int
Name string
Spec any
Labels map[string]string
}
ChildSpec describes one child a composer wants to exist under a parent. Spec is the typed Go value (e.g. AccountSpec); the core JSON-marshals at the DB boundary.
type ComposePipelineResult ¶
type ComposePipelineResult struct {
Desired []ChildSpec
Edges []DepEdge
Configs []ProviderConfigSpec
Status json.RawMessage
Conditions []Condition
FailedStage int
Err error
}
ComposePipelineResult is the compose wire output: desired children/edges/configs + status/conditions (and an error with the failing stage index). It is the only *PipelineResult still carried on the wire — work/rollup/delete/operate results ride back as the bare Outcome fields (status/conditions/output) in StageComplete, so they need no dedicated result DTO.
type ComposeRequest ¶
type ComposeRequest struct {
Root Resource
Observed []Resource
Desired []ChildSpec
Edges []DepEdge
Configs []ProviderConfigSpec
Env *Env
}
ComposeRequest is the compose wire input: the root + its observed children (Desired/Edges/Configs are accumulator fields the wire carries; the single-handler model leaves them empty on input and fills the Outcome instead).
type Condition ¶
type Condition struct {
Type string // "Ready", "Synced", or a custom axis
Status ConditionStatus // True | False | Unknown
Reason string // CamelCase machine code, e.g. "Available"
Message string // human-readable detail
}
Condition is one axis of a resource's status — K8s/Crossplane shape. Returned by a reaction in its Outcome's Conditions slice; the core upserts them into resource_conditions on transition and, for Type==TypeReady, folds Status into the health_ok scalar.
type ConditionStatus ¶
type ConditionStatus string
ConditionStatus mirrors metav1.ConditionStatus.
const ( ConditionTrue ConditionStatus = "True" ConditionFalse ConditionStatus = "False" ConditionUnknown ConditionStatus = "Unknown" )
type DeleteRequest ¶
DeleteRequest is the delete wire input.
type DepEdge ¶
type DepEdge struct {
From ResourceRef // dependent
To ResourceRef // dependency
Values []ValueFlow
}
DepEdge: dependent depends on dependency. Persisted to resource_deps. An edge optionally carries value flows that fill the dependent's spec from the upstream's status when the upstream becomes ready.
type Env ¶
type Env struct {
Logger *slog.Logger
ProviderConfig json.RawMessage
// ProviderBundle is the per-resource CUSTOM bundle override (the clone of the
// referenced custom config's `data` — the opaque artifact, e.g. a zip of
// Starlark .star files), or nil when the resource carries no custom bundle.
// Unlike ProviderConfig it does NOT field-merge: a handler computes its
// EFFECTIVE bundle by taking this override whole when non-empty, else the kind
// DEFAULT bundle (from the SDK's OnConfig/startup prime) — opaque bytes can't
// deep-merge. Use the SDK's converge.EffectiveBundle helper.
ProviderBundle []byte
}
Env is intentionally narrow: a handler describes what it wants done (children, status, output) and lets the core persist it. Handlers do not — and must not — issue raw SQL. Logger is scoped slog with task-specific fields (kind, resource id, generation).
ProviderConfig is the per-task CUSTOM config override: the clone of the resource's referenced providerconfig (work_queue.provider_config), or nil when the resource carries no custom override. It is pure read-only data (not a DB handle), so it fits Env's "per-call environment" role without widening what a handler can DO. Every handler of a task sees the same value. The kind DEFAULT config reaches the provider through the SDK (its OnConfig hook, primed at startup); a handler computes its EFFECTIVE config by overlaying this override onto that default (an object merge — the custom override wins per field, the default fills the rest) via the SDK's converge.EffectiveConfig. Spec is a separate axis and never participates.
type Kind ¶
type Kind string
Kind names a resource kind. String-aliased so call sites can use untyped string literals or typed constants interchangeably.
type KindManifest ¶
type KindManifest struct {
Kind Kind `json:"kind"`
// KindVersion is the web-API-style user-facing version (vpc/v1, vpc/v2, … — no
// minor/micro; 1–32767, the smallint ceiling of the resources/work_queue
// columns it is denormalized onto). A manifest is published PER (kind,
// kindVersion); the core routes work by (kind, kindVersion) exact equality. The
// kind string stays bare `vpc`; the kindVersion is this orthogonal field, never
// encoded into Kind. REQUIRED and explicit (>= 1): there is no implicit v1
// default — an un-versioned manifest is rejected at publish, never silently
// treated as v1 (no `omitempty`, so it always serializes).
KindVersion int `json:"kind_version"`
Description string `json:"description,omitempty"`
// JSON Schemas (RFC draft 2020-12) authored for the kind, so the runtime stores
// schema bytes, never a reflect.Type. A nil schema means "untyped" — the kind
// opts out of validation for that axis.
SpecSchema json.RawMessage `json:"spec_schema,omitempty"`
StatusSchema json.RawMessage `json:"status_schema,omitempty"`
ConfigSchema json.RawMessage `json:"config_schema,omitempty"`
// Reactions is the declared set of (Trigger, Emits) reactions. The core
// dispatches purely off these (it never reads a kind name, only the masks).
Reactions []ReactionDecl `json:"reactions"`
// FinalizerName, when set, makes deletes a two-phase teardown (the reaper
// soft-deletes and waits for the DeleteRequested reaction to strip it). Empty
// = immediate hard-delete.
FinalizerName string `json:"finalizer_name,omitempty"`
// Operational policy (today's kind_config knobs). Seeded insert-if-absent on
// apply so an operator's live /api/kinds/{kind}/config edits are never
// clobbered by a re-apply.
MaxInflight int `json:"max_inflight,omitempty"`
TaskDeadlineSecs int `json:"task_deadline_secs,omitempty"`
ResyncIntervalSecs int `json:"resync_interval_secs,omitempty"`
ResyncRecomposes bool `json:"resync_recomposes,omitempty"`
OrphanGraceSecs int `json:"orphan_grace_secs,omitempty"`
// MaxTransientAttempts caps consecutive TRANSIENT reconcile failures before the
// failure is escalated to terminal (dead-lettered), so a persistently-broken
// handler stops retrying forever. 0 (the default) = unbounded — set it to opt a
// kind into the poison-pill.
MaxTransientAttempts int `json:"max_transient_attempts,omitempty"`
// Retired SUNSETS this web-API version: a publish declaring it true (or an
// operator toggling it via /api/kinds/{kind}/config) freezes NEW creates/flips
// onto (kind, KindVersion) while EXISTING resources keep reconciling so they can
// drain or be migrated off — never a forced teardown. Seeded from the manifest
// but operator-editable at runtime, so it is preserved across a re-apply (the
// sync trigger never un-retires). Off the claim hot path.
Retired bool `json:"retired,omitempty"`
}
KindManifest is the CONTROL-PLANE half of a kind's definition: the CRD-style document an operator applies (its schemas, declared reactions, and lifecycle policy), validated by the legality lattice below and dispatched off by the outcome-mask vocabulary. A worker author never declares schemas, reactions, or policy — the manifest is applied out of band into the kind_manifest table, so none of this is on the public SDK surface.
The KindManifest IS the kind definition: the operator applies it as a CRD into the kind_manifest table and that row is the sole source of a kind's schemas, reactions, and policy. Its reactions are a single list of ReactionDecl whose (Trigger, Emits) pair encodes each reaction's behaviour.
KindManifest is the serializable CRD definition of a kind. It round-trips to the kind_manifest DB row (schemas as JSONB, reactions as JSONB) and to the API/UI manifest editor. JSON tags match the DB column names and the wire contract.
func (KindManifest) ComposerReaction ¶
func (m KindManifest) ComposerReaction() (ReactionDecl, bool)
ComposerReaction returns the (single, by the lattice) reaction that emits children on a spec change, and whether one exists. The core uses this to know a kind is a composer without reading its name.
func (KindManifest) DeleteReaction ¶
func (m KindManifest) DeleteReaction() (ReactionDecl, bool)
DeleteReaction returns the deleteRequested (finalizer teardown) reaction, and whether one exists — i.e. whether this kind runs a teardown on delete.
func (KindManifest) IsReactor ¶
func (m KindManifest) IsReactor() bool
IsReactor reports whether this kind is a REACTOR — a side-effect handler a binding SUBSCRIBES to other kinds' transitions (e.g. a status-upload sink), not a directly-creatable resource. A reactor declares ONLY reactor reactions: at least one TriggerReactor reaction and no specChange/childrenSettled/ deleteRequested/operation reaction. The API uses it to keep reactors out of the creatable-resource gate while still serving their config schema.
func (KindManifest) OperationReaction ¶
func (m KindManifest) OperationReaction(verb string) (ReactionDecl, bool)
OperationReaction returns the operation reaction bound to verb, and whether one exists — i.e. whether this kind exposes that subresource verb.
func (KindManifest) ReactorReaction ¶
func (m KindManifest) ReactorReaction() (ReactionDecl, bool)
ReactorReaction returns the (single) reactor reaction and whether one exists. A reactor CRD declares exactly one TriggerReactor reaction; its name is the handle the worker keys its handler by (the reaction the dispatcher ships when a binding to this reactor fires).
func (KindManifest) RollupReaction ¶
func (m KindManifest) RollupReaction() (ReactionDecl, bool)
RollupReaction returns the (single, by the lattice) childrenSettled+status reaction, and whether one exists — i.e. whether this kind is a rollup-er.
func (KindManifest) WorkerReaction ¶
func (m KindManifest) WorkerReaction() (ReactionDecl, bool)
WorkerReaction returns the specChange reaction that emits status WITHOUT children (a plain leaf worker), and whether one exists.
type KindVersion ¶
KindVersion is a (kind, web-API version) pair — the addressable unit of everything versioned: what a provider serves, what a worker advertises, what the broker routes, what a config-cache slot holds. Version is EXPLICIT (>= 1); there is no implicit v1. This is the ONE pair type across the SDK — providers, the worker client, and the remote-worker transport all use it, so no layer re-defines or converts the concept.
type OperateRequest ¶
OperateRequest is the operate wire input: the resource + the operation row.
type Operation ¶
type Operation struct {
// No ID field: the resource_operations row id is a pure internal DB key the
// broker uses to correlate the result; a provider never needs it. ResourceID
// (the resource's public uuid) identifies what the verb runs against.
ResourceID uuid.UUID
Verb string
Input json.RawMessage
Attempts int
RequestedBy string
}
Operation is the provider-side view of a resource_operations row, passed to an operation reaction's handler.
type Outcome ¶
type Outcome struct {
Status json.RawMessage // work/compose/rollup status
Conditions []Condition // health/custom axes
Children []ChildSpec // composer: desired children
Edges []DepEdge // composer: dep edges (+ value flows)
Configs []ProviderConfigSpec // composer: provider configs it owns
OperationOutput json.RawMessage // operate: verb output
SideEffectDone bool // lifecycle: the side effect completed (informational)
}
Outcome carries everything a reaction may produce. A handler fills only the parts its reaction declared in Emits; the core applies exactly those (a part set without the matching Emits bit is ignored, and the manifest legality lattice forbids the dangerous combinations). Status is last-writer-wins; Conditions accumulate.
type OutcomeBit ¶
type OutcomeBit string
OutcomeBit names one applicable Outcome part.
const ( OutcomeChildren OutcomeBit = "children" // ApplyComposeResult (graph upsert/prune) OutcomeEdges OutcomeBit = "edges" // dep edges (with value-flow) OutcomeConfigs OutcomeBit = "configs" // provider configs the composer owns OutcomeStatus OutcomeBit = "status" // resources.status OutcomeConditions OutcomeBit = "conditions" // resource_conditions / health_ok OutcomeFinalizer OutcomeBit = "finalizer" // strip the finalizer (delete path) OutcomeOperationOutput OutcomeBit = "operationOutput" // resource_operations.output OutcomeSideEffect OutcomeBit = "sideEffect" // external side effect only; ack the delivery )
type OutcomeMask ¶
type OutcomeMask []OutcomeBit
OutcomeMask is the set of Outcome parts the core will APPLY for a reaction — the SECOND half of a reaction's identity. A reaction author declares it so the core knows which fields of the returned Outcome are meaningful (and validates legality, e.g. only a Children-emitting reaction can mutate the graph). It serializes as a JSON array of OutcomeBit strings.
func (OutcomeMask) Has ¶
func (m OutcomeMask) Has(bit OutcomeBit) bool
Has reports whether the mask includes bit.
type ProviderConfigSpec ¶
type ProviderConfigSpec struct {
Name string
Kind Kind
// KindVersion is the web-API version of the consumer Kind this config is for. A
// composer configuring a v2 child emits its config at KindVersion 2 so the config's
// (kind, kindVersion) matches the child it attaches to (the resource-apply guard
// enforces the match). REQUIRED and explicit (>= 1): there is no implicit v1
// default — a composer MUST set the version its config is for.
KindVersion int
IsDefault bool
Spec any
}
ProviderConfigSpec is one provider config a composer emits, alongside its children and edges. The handler chain threads accumulated configs (like Children/Edges) and ApplyComposeResult diffs them against the configs this root already OWNS — upserting the changed/new delta and pruning what vanished, all owned by the root so they GC with it (a composer's emitted configs never outlive the composer root). Spec is the config document; its shape is the consumer kind's manifest config_schema.
- IsDefault=true → the consumer kind's single live-reconfigurable DEFAULT (editing it pushes to every worker; one per kind globally, enforced by uq_providerconfigs_default_per_kind).
- IsDefault=false → a named CUSTOM config a resource references by Name via provider_config_ref.
Names are a GLOBAL handle, so a composer must name its configs uniquely (e.g. prefixed by the root) exactly like children — a name already owned by a different root is left untouched (the owner-guarded upsert no-ops).
type ReactRequest ¶
type ReactRequest struct {
Resource Resource
Transition Transition
DedupToken string
Env *Env
}
ReactRequest is the lifecycle-reactor wire input: the resource that crossed a transition, the transition name, and the dedup token (the stable per-binding key an idempotent reactor uses to no-op a redelivery). Status rides in the Resource; the sink config rides in Env.ProviderConfig (like every other stage).
type ReactionDecl ¶
type ReactionDecl struct {
Name string `json:"name"`
Trigger Trigger `json:"trigger"`
Emits OutcomeMask `json:"emits"`
// Verb refines TriggerOperation: the subresource verb this reaction handles.
Verb string `json:"verb,omitempty"`
// Finalizer refines TriggerDeleteRequested: the finalizer string this teardown
// strips (defaults to the manifest's FinalizerName when empty).
Finalizer string `json:"finalizer,omitempty"`
}
ReactionDecl is one declared reaction. name is the stable handle the worker keys its handler by (and the lifecycle_outbox binding name). trigger + emits drive the core's dispatch and apply. The optional fields refine specific triggers. Trigger is the author-facing Trigger enum; Emits is the control-plane OutcomeMask.
type ReactionRequest ¶
type ReactionRequest struct {
Reaction string // the ReactionDecl.Name — selects the handler
KindVersion int // the resource's web-API version (>= 1) — lets a Work serving several versions of one kind route (vpc/v1 vs vpc/v2)
Trigger Trigger // why it fired
Resource Resource // the subject resource, with its current Spec/Status
Status json.RawMessage // accumulated status (work/compose/rollup); nil for the first
Observed []Resource // composer: current owned children + statuses
Descendants []Resource // rollup: the whole settled subtree
Operation *Operation // operate: the resource_operations row
Transition Transition // reactor: the lifecycle edge that fired (a TransitionX constant)
Generation int64 // the generation that fired (pin reads/keys to it)
DedupToken string // lifecycle: stable "<id>:<transition>:<gen>:<reaction>" for non-idempotent sinks
Env *Env // logger + this resource's per-resource config override (merge with the default in the handler)
}
ReactionRequest carries everything a reaction may need. The core fills only the fields a given reaction needs (driven by its Trigger): SpecChange/Work gets Resource+Status; a composer also reads Observed; a rollup gets Descendants; operate gets Operation; lifecycle gets Transition+DedupToken. Reaction + Trigger tell the worker which handler to run and why.
type Resource ¶
type Resource struct {
ID uuid.UUID
Kind Kind
Name string
OwnerID *uuid.UUID
RootID *uuid.UUID
Spec json.RawMessage
Status json.RawMessage
Generation int64
SyncedGen int64
IsReady bool
Finalizers []string
DeletionRequestedAt *time.Time
// FrozenUntil and Quarantined are the two faces of the single resources
// frozen_until column (orphaned = a finite future instant; quarantined =
// 'infinity'), split here so providers read each unambiguously:
//
// FrozenUntil — non-nil means the composer dropped this child and it is in
// the orphan-grace window (phase=Orphaned), awaiting a re-emit (which clears
// it) or the reaper's sweep. It is the finite teardown deadline.
//
// Quarantined — true when an operator set this failed resource ASIDE
// (phase=Quarantined, frozen_until='infinity').
//
// A rollup MUST treat either as "ignore this child" (use IsFrozen): an
// orphaned child has left the composition, and a quarantined child is
// deliberately excluded so one bad child can't block the root.
FrozenUntil *time.Time
Quarantined bool
Labels map[string]string
}
Resource is what a reaction handler receives. JSON columns are json.RawMessage so handlers can re-marshal without conversion.
State is observed via IsReady (GENERATED from synced_gen >= generation AND health_ok AND no pending deletion) — the Synced and Ready axes folded into one queryable flag. There is no conditions array on the row and no separate observed_generation; synced_gen carries the synced axis (plus composed_gen for the compose-skip gate).
type ResourceManifest ¶
type ResourceManifest struct {
Kind Kind `json:"kind"`
Name string `json:"name"`
Labels map[string]string `json:"labels"`
Spec json.RawMessage `json:"spec"`
}
ResourceManifest is the self-describing document for a single resource: its identity (Kind + Name), Labels, and Spec bundled into one object. It is the canonical shape both on disk (the resource JSON fixtures) and on the wire (the Apply request body and the download/synthesize response). Carrying kind/name/labels alongside the spec means a manifest applies on its own, with all of its identity in the one document.
{
"kind": "some-kind",
"name": "example-1",
"labels": {"team": "core"},
"spec": { ... }
}
Spec is kept as raw JSON so a manifest round-trips an arbitrary per-kind spec without this package knowing its shape, and so large specs (tens of MB) are never re-marshaled. Labels is a flat string→string map, mirroring resource_meta.labels (JSONB object); it has no omitempty so an empty label set serializes as {} (the canonical on-disk shape) rather than vanishing — and it must be a non-nil map at marshal time for that to hold.
type ResourceRef ¶
ResourceRef identifies a sibling resource by (Kind, Name) — the natural key under a single owner. Composers and the provider framework use ResourceRef in DepEdge and ChildSpec; the on-disk row uses a UUID id, not a ref.
func (ResourceRef) String ¶
func (r ResourceRef) String() string
type RollupRequest ¶
type RollupRequest struct {
Root Resource
Descendants []Resource
Status json.RawMessage
Env *Env
}
RollupRequest is the rollup wire input: the root + its settled subtree.
type StageDispatcher ¶
type StageDispatcher interface {
// kindVersion is the resource's web-API version (work_queue.kindVersion, carried from the
// claim) and is ALWAYS explicit (>= 1) — there is no implicit v1 default. A
// dispatcher that routes to versioned handlers (in-process registry, or a
// remote worker serving several kindVersions) uses it to select the
// (kind, kindVersion)'s handler; a 0/negative value has no handler and is a hard
// lookup miss (a real failure, never silently v1).
DispatchStage(ctx context.Context, kind Kind, kindVersion int, rx ReactionDecl, req ReactionRequest) (Outcome, int, error)
}
───────────────────────────────────────────────────────────────────────── StageDispatcher — the dispatch seam between the reconcile engine and wherever a reaction's handler actually RUNS.
The core's reconcile engine (internal/runtime) builds a ReactionRequest from a claimed DB row and calls DispatchStage; the IMPLEMENTATION decides WHERE the handler runs and returns its Outcome:
- remote (the broker's fanout, the ONLY production impl): the dispatcher does NOT execute — it ships the stage to a dumb worker over Connect and awaits the worker's result. Routing/leases/heartbeats are the broker's job; running the handler is the worker's.
- in-process (test only, test/internal/inproc): encode the stage to a proto StageTask and run it directly against the registered providers — the zero-broker path the integration suite drives.
Either way the ENGINE does all the DB reads/writes AROUND the stage (the gate reads, ApplyComposeResult, AppendOutbox) and applies the returned Outcome; the dispatcher only gets the handler run and hands the Outcome back. The single manifest-driven call means the engine never threads typed per-kind funcs.
It takes a ReactionDecl (the manifest-declared reaction being dispatched) but speaks the author-facing ReactionRequest/Outcome — a worker author never implements or calls it; only the broker/engine/test executors do.
Returns the Outcome, the failing handler index (for the error label), and the error (nil on success). A stage whose handler is not present is an error.
type Transition ¶
type Transition string
Transition is a resource lifecycle edge a reactor_bindings subscription may watch — the set a `reactor` reaction fires on. It is delivered to the reactor handler as data (ReactionRequest.Transition). The constants are the ONLY legal values; they mirror the DB CHECK on reactor_bindings.transition / lifecycle_outbox.transition (both must agree), and the huma enum tag on the reactor-binding apply body validates an incoming value against this same set at the API edge. Use these — never a string literal — so a typo is a compile error, not a binding that silently never fires.
const ( // TransitionCreated fires when a resource is first applied (from the apply tx). TransitionCreated Transition = "created" // TransitionSynced fires when a resource reaches synced_gen >= generation (the // durable "rolled up / reconciled" edge — the cascade computes it). TransitionSynced Transition = "synced" // TransitionDegraded fires when a resource's health flips true→false. TransitionDegraded Transition = "degraded" // TransitionFailed fires when a resource newly hard-fails (failure_gen == generation). TransitionFailed Transition = "failed" // TransitionDeleted fires when a resource is torn down. TransitionDeleted Transition = "deleted" )
type Trigger ¶
type Trigger string
Trigger is the condition that fires a reaction — the FIRST half of a reaction's identity; the SECOND half is the Emits mask (OutcomeMask). The core matches a claimed task / lifecycle delivery to the reaction(s) whose Trigger applies, ships the reaction, and applies the Outcome ∩ Emits.
const ( // TriggerSpecChange fires when generation bumps (a spec edit / first apply). // Emits=Children → compose; Emits=Status → work. A kind can declare both. TriggerSpecChange Trigger = "specChange" // TriggerChildrenSettled fires when a root is re-pended after its whole // subtree settled (the cascade edge). Emits=Status → rollup. The ONLY trigger // for which the core loads the descendant subtree. TriggerChildrenSettled Trigger = "childrenSettled" // TriggerDeleteRequested fires when a finalizer-bearing resource is being torn // down. Emits=Finalizer → the core strips the finalizer string on success. TriggerDeleteRequested Trigger = "deleteRequested" // TriggerOperation fires when a resource_operations row is enqueued for a Verb. // Emits=OperationOutput → the core writes the verb output. TriggerOperation Trigger = "operation" // TriggerReactor marks a REACTOR reaction: a pure side effect that a // reactor_bindings SUBSCRIPTION delivers when a WATCHED kind crosses a // transition (created|synced|degraded|failed|deleted). Emits=SideEffect only. // The reactor CRD declares just this reaction (name + emits) — NO transition, // kind, or label; those live on the binding. At delivery the transition that // fired is passed to the handler as DATA (ReactionRequest.Transition), not // baked into the reaction's identity. The core acks the lifecycle_outbox row // on success (at-least-once). TriggerReactor Trigger = "reactor" // TriggerResync fires on a drift tick (no generation bump). Re-runs the // SpecChange-class reactions to re-observe health; not a distinct handler. TriggerResync Trigger = "resync" )
type ValueFlow ¶
ValueFlow declares one field-level flow on a DepEdge: the dependent's spec[DependentField] is filled from the upstream's status[SourceField] when the upstream's status becomes available.
Both fields are RFC 6901 JSON Pointers ("/account_id"); a bare key without a leading slash is treated as a single-segment pointer. The flow is applied as a JSON-pointer COPY (status[SourceField] → spec[DependentField]), guarded by present-and-non-null — an absent source field simply does not fire (no error), and the copied value is trusted as-is (it is the upstream's own status). The fields are NOT individually schema-checked; the guarantee is the DEPENDENT's own spec_schema, which the store validates when the composer emits the child (with DependentField relaxed, since the flow fills it later). So a flow into a kind with a closed spec_schema is bounded by that schema (minus the deferred field); a flow into an opaque (additionalProperties:true) kind is trusted by design.
type WorkRequest ¶
type WorkRequest struct {
Resource Resource
Status json.RawMessage
Env *Env
}
WorkRequest is the work wire input: the resource + accumulated status.