conditions

package
v0.44.0 Latest Latest
Warning

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

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

Documentation

Overview

Package conditions implements the condition "timer"/"threshold"/"counter" block family.

Phase 1 provides the shared infrastructure only: the behavior attribute bundle common to every subtype, and the state machine that drives activate_after / deactivate_after / timeout / cooldown / latch / invert semantics. Subtype registrations (timer, threshold, counter) are added in later phases.

Index

Constants

This section is empty.

Variables

View Source
var CounterConditionCapsuleType = cty.CapsuleWithOps("counter_condition",
	reflect.TypeOf((*CounterCondition)(nil)).Elem(), &cty.CapsuleOps{
		GoString:     func(v interface{}) string { return fmt.Sprintf("counter_condition(%p)", v) },
		TypeGoString: func(_ reflect.Type) string { return "CounterCondition" },
	})
View Source
var FlipflopConditionCapsuleType = cty.CapsuleWithOps("flipflop_condition",
	reflect.TypeOf((*FlipflopCondition)(nil)).Elem(), &cty.CapsuleOps{
		GoString:     func(v interface{}) string { return fmt.Sprintf("flipflop_condition(%p)", v) },
		TypeGoString: func(_ reflect.Type) string { return "FlipflopCondition" },
	})
View Source
var ThresholdConditionCapsuleType = cty.CapsuleWithOps("threshold_condition",
	reflect.TypeOf((*ThresholdCondition)(nil)).Elem(), &cty.CapsuleOps{
		GoString:     func(v interface{}) string { return fmt.Sprintf("threshold_condition(%p)", v) },
		TypeGoString: func(_ reflect.Type) string { return "ThresholdCondition" },
	})
View Source
var TimerConditionCapsuleType = cty.CapsuleWithOps("timer_condition",
	reflect.TypeOf((*TimerCondition)(nil)).Elem(), &cty.CapsuleOps{
		GoString:     func(v interface{}) string { return fmt.Sprintf("timer_condition(%p)", v) },
		TypeGoString: func(_ reflect.Type) string { return "TimerCondition" },
	})

Functions

This section is empty.

Types

type Behavior

type Behavior struct {
	ActivateAfter   time.Duration
	DeactivateAfter time.Duration
	Timeout         time.Duration
	Cooldown        time.Duration
	Latch           bool
	Invert          bool
	Retentive       bool
	StartActive     bool
	Inhibit         hcl.Expression
}

Behavior holds the attributes common to every condition subtype. Zero-valued durations disable their respective delays. Inhibit is captured as the raw HCL expression; Phase 2 (reactive expression infrastructure) wires it into the state machine.

type Clock

type Clock interface {
	Now() time.Time
	AfterFunc(d time.Duration, f func()) ClockTimer
}

Clock abstracts time so the state machine is testable. Production code uses RealClock; tests use a fake implementation.

type ClockTimer

type ClockTimer interface {
	Stop() bool
}

ClockTimer is the subset of time.Timer the state machine uses.

type CounterCondition

type CounterCondition struct {
	// contains filtered or unexported fields
}

CounterCondition is the `condition "counter"` subtype. It tracks a running integer count via increment()/decrement() and produces a boolean output via the shared StateMachine when the count reaches a configured preset.

Counter does not support input=, debounce, or retentive (per spec §Attribute Applicability) — those are rejected at parse time. The shared behavioral attributes (activate_after / deactivate_after / timeout / latch / invert / cooldown / inhibit) are honored.

func (*CounterCondition) Clear added in v0.35.0

func (c *CounterCondition) Clear(ctx context.Context) error

Clear implements richcty.Clearable. For counters there is no `input =` to re-sample (counters reject input=), so clear() is equivalent to reset(): it empties the count (and the windowed FIFO), releases any latch, and returns the output to inactive.

func (*CounterCondition) Count

func (c *CounterCondition) Count(_ context.Context) (int64, error)

Count implements richcty.Countable: returns the current numeric count value. Distinct from Get(), which returns the boolean preset-reached output.

In windowed mode, expired events are pruned opportunistically before the count is read so that a quiescent counter still reports an accurate value between expiry-timer firings.

func (*CounterCondition) Get

func (c *CounterCondition) Get(ctx context.Context, args []cty.Value) (cty.Value, error)

func (*CounterCondition) Increment

func (c *CounterCondition) Increment(ctx context.Context, args []cty.Value) (cty.Value, error)

Increment implements richcty.Incrementable. Adds args[0] (default 1) to the count, applies low-side clamp at 0 (per spec — applies to decrement under rollover=false; we apply uniformly since a bare increment with negative delta is decrement), then re-evaluates the preset comparison and drives the state machine. With rollover=true, reaching the preset auto-resets the count to initial; under latch=true the latched output rides through the auto-reset so no spurious deactivate edge fires.

func (*CounterCondition) PostStart added in v0.35.0

func (c *CounterCondition) PostStart() error

PostStart fires the on_init hook after all Startables have bootstrapped. Implements config.PostStartable.

func (*CounterCondition) Reset

func (c *CounterCondition) Reset(ctx context.Context) error

Reset implements richcty.Resettable: count → initial, latch released, pending state cancelled, output → inactive. Spec §Functions. In windowed mode, the FIFO of in-window events is also discarded and the expiry timer cancelled.

func (*CounterCondition) Start

func (c *CounterCondition) Start() error

func (*CounterCondition) State

func (c *CounterCondition) State(ctx context.Context) (string, error)

func (*CounterCondition) Stop

func (c *CounterCondition) Stop() error

func (*CounterCondition) Unwatch

func (c *CounterCondition) Unwatch(w richcty.Watcher)

func (*CounterCondition) Watch

func (c *CounterCondition) Watch(w richcty.Watcher)

type FlipflopCondition added in v0.41.0

type FlipflopCondition struct {
	// contains filtered or unexported fields
}

FlipflopCondition is the `condition "flipflop"` subtype. It exposes the standard digital-logic bistables — T, SR, gated SR, D, D-latch, JK — through a uniform set of "wire" attributes. Each declared wire is a boolean HCL expression observed reactively; combinations of wires name the variant.

The flipflop resolves its wires to a single desired output value each cycle (applying the conflict-resolution priority: gate → set/reset dominance → set/reset over toggle → D-sample over toggle) and feeds that value to the shared StateMachine via SetRawInput. The StateMachine owns latch / invert / cooldown / inhibit and the four-state output model, so those behaviors carry over identically from timer. The flipflop deliberately does NOT use the temporal StateMachine behaviors (activate_after / deactivate_after / timeout / retentive): a flipflop responds to its inputs immediately.

Edge detection is per-wire: each wire stores its previous boolean and a hasPrev flag; the first evaluation (at Start, or the first notification) establishes the baseline and reports no edge, so a source that happens to be asserting at boot does not spuriously drive the flipflop. start_active is the explicit boot-output knob.

Dispatch: rather than rely on the source identity reported by OnChange (which for condition sources is the inner StateMachine, not the capsule a wire referenced — see flipflopSource), the flipflop subscribes one flipflopSource watcher per referenced Watchable, each carrying the wires that reference it. A notification thus re-evaluates only the wires fed by the source that fired; the gate's level is cached so non-gate notifications never re-evaluate it.

func (*FlipflopCondition) Clear added in v0.41.0

func (f *FlipflopCondition) Clear(ctx context.Context) error

Clear implements richcty.Clearable. Resets the desired output to false and resets the state machine (releasing any latch, restarting cooldown). The wires keep their edge baselines, so the next genuine edge re-drives output.

func (*FlipflopCondition) Get added in v0.41.0

func (f *FlipflopCondition) Get(ctx context.Context, args []cty.Value) (cty.Value, error)

func (*FlipflopCondition) PostStart added in v0.41.0

func (f *FlipflopCondition) PostStart() error

PostStart fires the on_init hook after all Startables have bootstrapped.

func (*FlipflopCondition) Set added in v0.41.0

func (f *FlipflopCondition) Set(ctx context.Context, args []cty.Value) (cty.Value, error)

Set implements richcty.Settable. Unlike timer (which rejects set() once an input= is declared), a flipflop accepts imperative set() even with wires declared — the wires are additional event sources, not the only sources. Honors latch / inhibit / cooldown via the normal SetRawInput path (e.g. set(false) is ignored while latched-active).

func (*FlipflopCondition) Start added in v0.41.0

func (f *FlipflopCondition) Start() error

Start bootstraps the state machine, establishes the per-wire baselines without firing any edge (including the cached gate assertion), starts the inhibit expression, and subscribes each source. No synthetic boot transition is produced — start_active is the only boot-output knob.

func (*FlipflopCondition) State added in v0.41.0

func (f *FlipflopCondition) State(ctx context.Context) (string, error)

func (*FlipflopCondition) Stop added in v0.41.0

func (f *FlipflopCondition) Stop() error

Stop unsubscribes every source and stops the inhibit expression.

func (*FlipflopCondition) Toggle added in v0.41.0

func (f *FlipflopCondition) Toggle(ctx context.Context, _ []cty.Value) (cty.Value, error)

Toggle implements richcty.Toggleable. Equivalent to set(condition.x, !current), flipping the desired output directly. Always permitted. Returns the new value.

func (*FlipflopCondition) Unwatch added in v0.41.0

func (f *FlipflopCondition) Unwatch(w richcty.Watcher)

func (*FlipflopCondition) Watch added in v0.41.0

func (f *FlipflopCondition) Watch(w richcty.Watcher)

type HookDispatcher added in v0.35.0

type HookDispatcher struct {
	// contains filtered or unexported fields
}

HookDispatcher evaluates condition lifecycle hooks. It is registered as an internal Watcher on the condition's StateMachine so on_activate / on_deactivate fire synchronously on every output transition; on_init fires once from the subtype's PostStart().

Error handling: any diagnostics from building the eval context or evaluating the hook expression are logged to UserLogger and do not propagate — matches the existing trigger "watch" behaviour.

Context propagation: OnChange receives the ctx from sm.notifyAll(ctx, ...). That ctx carries any caller trace span (e.g. from an inbound message that drove the transition) or is context.Background() for autonomous timer- driven transitions; either way the hook opens a child/root span named "trigger.condition.<hook> <name>".

func NewHookDispatcher added in v0.35.0

func NewHookDispatcher(name string, hooks Hooks, config *cfg.Config, tp trace.TracerProvider) *HookDispatcher

NewHookDispatcher constructs a dispatcher. Returns nil when no hooks are configured so callers can skip the Watch() registration entirely.

func (*HookDispatcher) FireInit added in v0.35.0

func (h *HookDispatcher) FireInit(currentOutput bool)

FireInit evaluates on_init once. Called from the owning subtype's PostStart() with the condition's current output.

func (*HookDispatcher) OnChange added in v0.35.0

func (h *HookDispatcher) OnChange(ctx context.Context, _ richcty.Watchable, old, new cty.Value)

OnChange implements richcty.Watcher. StateMachine already guarantees output changes, so the defensive equal check is only for safety.

type Hooks added in v0.35.0

type Hooks struct {
	OnInit       hcl.Expression
	OnActivate   hcl.Expression
	OnDeactivate hcl.Expression
}

Hooks bundles the three condition lifecycle action expressions. Zero-valued (nil) expressions are treated as "not configured" and skipped. Kept as a shared value type even though gohcl cannot inline-promote its fields into subtype body structs — the runtime plumbing (HookDispatcher) is shared.

type RealClock

type RealClock struct{}

RealClock is a Clock backed by the stdlib time package.

func (RealClock) AfterFunc

func (RealClock) AfterFunc(d time.Duration, f func()) ClockTimer

func (RealClock) Now

func (RealClock) Now() time.Time

type State

type State int

State is the four-state vocabulary shared by every condition subtype.

const (
	StateInactive State = iota
	StatePendingActivation
	StateActive
	StatePendingDeactivation
)

func (State) String

func (s State) String() string

type StateMachine

type StateMachine struct {
	richcty.WatchableMixin
	// contains filtered or unexported fields
}

StateMachine implements the four-state output logic shared by every condition subtype. It consumes a single boolean "raw" input signal (already filtered by the subtype — e.g. through debounce for timer, hysteresis for threshold, preset comparison for counter) and exposes the conditioned output with temporal semantics layered on top.

StateMachine is Watchable (fires on output transitions only — never on pending-state transitions), Gettable (returns the output bool, inverted if configured), Stateful (returns the internal four-state name), and Clearable (cancels any pending state / releases any latch).

Zero-valued Behavior fields disable their respective behaviors; the StateMachine with a fully-zeroed Behavior simply tracks the raw input one-to-one.

func NewStateMachine

func NewStateMachine(behavior Behavior, clock Clock) *StateMachine

NewStateMachine creates a StateMachine with the given behavior. If clock is nil, RealClock is used.

func (*StateMachine) Bootstrap added in v0.35.0

func (sm *StateMachine) Bootstrap()

Bootstrap applies the configured initial state. Must be called exactly once, from the owning subtype's Start() method, before any input is pushed to the state machine. If behavior.StartActive is false, this is a no-op.

When StartActive is true: state is forced to Active. If behavior.Latch is also true, the latch is engaged (only clear()/reset() can release it). If Latch is false and Timeout > 0, the timeout timer is armed. No NotifyAll fires — boot-active is a silent initial condition, not a synthetic rising edge.

Bootstrap does NOT consult inhibit: inhibit prevents new activations, and a configured initial state is not an activation. Invert is applied by Output() as usual, so start_active + invert produces external get() == false.

func (*StateMachine) Clear

func (sm *StateMachine) Clear(ctx context.Context) error

Clear implements richcty.Clearable. Resets the state machine to Inactive, cancels any pending state, releases any latch, and (for subtypes backing them onto this machine) stops timers. Safe to call in any state.

func (*StateMachine) Get

func (sm *StateMachine) Get(_ context.Context, _ []cty.Value) (cty.Value, error)

Get implements richcty.Gettable. The optional default argument is ignored: a condition always has a defined output.

func (*StateMachine) Output

func (sm *StateMachine) Output() bool

Output returns the current boolean output (post-inversion). Safe for concurrent use.

func (*StateMachine) RawInput added in v0.35.0

func (sm *StateMachine) RawInput() bool

RawInput returns the most recent value tracked by the state machine. Subtype wrappers use this to reconcile their own input cache after the state machine has auto-changed its view of the input (e.g. via timeout-driven deactivation or Clear()).

func (*StateMachine) SetInhibited

func (sm *StateMachine) SetInhibited(ctx context.Context, inhibited bool)

SetInhibited updates the inhibit gate. When inhibit is asserted and the machine is currently in pending_activation, the pending transition is cancelled and (for retentive timers) the accumulator is discarded. Active and pending_deactivation states are unaffected per spec: inhibit prevents new activations, it does not force deactivation. When inhibit clears while the underlying input is still asserted, activation resumes from scratch (activate_after restarts; retentive accumulator is already zeroed).

func (*StateMachine) SetRawInput

func (sm *StateMachine) SetRawInput(ctx context.Context, value bool)

SetRawInput drives the state machine from the subtype's pre-filtered boolean input signal. ctx is forwarded to watcher notifications.

func (*StateMachine) State

func (sm *StateMachine) State(_ context.Context) (string, error)

State implements richcty.Stateful, returning the internal four-state name.

type ThresholdCondition

type ThresholdCondition struct {
	// contains filtered or unexported fields
}

ThresholdCondition is the `condition "threshold"` subtype. It derives a boolean from a numeric input using separate on/off thresholds (hysteresis) and drives the shared StateMachine with that derived boolean — so activate_after / deactivate_after / timeout / cooldown / latch / invert / retentive semantics carry over identically from timer. `input =` is required (numeric, reactive only); there is no imperative set().

func (*ThresholdCondition) Clear

func (c *ThresholdCondition) Clear(ctx context.Context) error

Clear resets the hysteresis state machine and cancels any in-flight debounce. The declared input is re-sampled afterward and hysteresis is applied from the freshly-reset baseline (derived=false): a value above the on threshold re-activates the condition; values inside the deadband leave it inactive (consistent with the first-sample initial-value rule). When re-activation occurs and latch is configured, the latch re-engages automatically — clear() releases the latch but does not silence an ongoing-true input. Debounce is bypassed on the re-activation edge since the signal has already proven stable.

func (*ThresholdCondition) Get

func (c *ThresholdCondition) Get(ctx context.Context, args []cty.Value) (cty.Value, error)

func (*ThresholdCondition) PostStart added in v0.35.0

func (c *ThresholdCondition) PostStart() error

PostStart fires the on_init hook after all Startables have bootstrapped. Implements config.PostStartable.

func (*ThresholdCondition) Start

func (c *ThresholdCondition) Start() error

func (*ThresholdCondition) State

func (c *ThresholdCondition) State(ctx context.Context) (string, error)

func (*ThresholdCondition) Stop

func (c *ThresholdCondition) Stop() error

func (*ThresholdCondition) Unwatch

func (c *ThresholdCondition) Unwatch(w richcty.Watcher)

func (*ThresholdCondition) Watch

func (c *ThresholdCondition) Watch(w richcty.Watcher)

type TimerCondition

type TimerCondition struct {
	// contains filtered or unexported fields
}

TimerCondition is the `condition "timer"` subtype. It takes a boolean input (either supplied imperatively via set() or declared reactively via input=), applies optional debounce pre-filtering, and drives a StateMachine that owns the temporal semantics (activate_after / deactivate_after / timeout / cooldown / latch / invert / retentive).

func (*TimerCondition) Clear

func (t *TimerCondition) Clear(ctx context.Context) error

Clear implements richcty.Clearable. In addition to resetting the state machine (cancelling pending state, releasing latch, discarding retentive accumulation), it cancels any in-flight debounce.

When a declared input expression is currently truthy, the rising edge is re-asserted into the state machine so the condition re-activates (and, if latch is configured, re-latches): clear() releases the latch but does not silence an ongoing-true input. Debounce is bypassed — the signal has already proven stable through the debounce window. activate_after, cooldown, and inhibit gating still apply via the normal SetRawInput path.

func (*TimerCondition) Get

func (t *TimerCondition) Get(ctx context.Context, args []cty.Value) (cty.Value, error)

Get implements richcty.Gettable.

func (*TimerCondition) PostStart added in v0.35.0

func (t *TimerCondition) PostStart() error

PostStart fires the on_init hook after all Startables have bootstrapped. Implements config.PostStartable. Called by the config runtime exactly once, in Startables order, after the Startables phase completes. No-op when no hooks were configured (hooks is nil and the subtype was not added to PostStartables in that case, so this method should not be invoked — the nil-guard in FireInit makes it safe regardless).

func (*TimerCondition) Set

func (t *TimerCondition) Set(ctx context.Context, args []cty.Value) (cty.Value, error)

Set implements richcty.Settable. Rejects calls when a declarative input= was configured (per spec §Functions).

func (*TimerCondition) Start

func (t *TimerCondition) Start() error

Start is invoked from the config Startables phase. It wires up the reactive input and inhibit expressions; each subscribes to its Watchables and pushes initial values into the condition.

func (*TimerCondition) State

func (t *TimerCondition) State(ctx context.Context) (string, error)

State implements richcty.Stateful.

func (*TimerCondition) Stop

func (t *TimerCondition) Stop() error

Stop unsubscribes reactive expressions so the condition stops reacting to upstream changes during shutdown.

func (*TimerCondition) Toggle added in v0.37.0

func (t *TimerCondition) Toggle(ctx context.Context, _ []cty.Value) (cty.Value, error)

Toggle implements richcty.Toggleable. Equivalent to set(condition.x, !current), flipping the bistable's input through the normal debounce + state-machine path. Rejected when a declared input= expression is present (the condition is then level-tracking, not bistable — same rule as Set). Extra args are ignored. Returns the new input boolean.

The basis for the flip is the post-debounce stableInput, reconciled against the state machine's RawInput() first to pick up auto-resets from timeout or Clear() (mirrors the reconcile in submitInput). All gating — activate_after, deactivate_after, cooldown, latch, inhibit, invert — applies as for any other input edge.

func (*TimerCondition) Unwatch

func (t *TimerCondition) Unwatch(w richcty.Watcher)

func (*TimerCondition) Watch

func (t *TimerCondition) Watch(w richcty.Watcher)

Watch implements richcty.Watchable by forwarding to the state machine, whose watcher notifications already respect pending-state suppression and invert.

Jump to

Keyboard shortcuts

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