automation

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package automation evaluates a workspace's standing instructions and carries them out.

**Nothing here runs on the request path, and nothing can put it there.** The only entry point is Evaluate, the only caller is the scheduler, and the scheduler calls it under the Postgres advisory lock every other job takes. A redirect never touches this package; neither does a link write. That is the first of the two claims m43.md turns on, and the import graph is what enforces it — internal/link and internal/redirect do not import this package, and this package's dependencies are all interfaces it declares itself.

**The per-run cost is a product of four constants, all of them in internal/domain.** A run reads at most domain.AutomationRulesPerRun rules; each rule runs one indexed range query bounded at domain.AutomationMatchesPerRule; each rule runs at most domain.MaxAutomationActions actions, of which only the archive is per-subject; and one further statement closes the run by advancing the cursor over every rule it looked at. The arithmetic is written out beside those constants rather than left for a reader to assemble, because m43.md's Risks say the bound is the thing that has to be true and a bound nobody can state is not one.

**Every rule comes round, and the column that makes that true is not the watermark.** The cap means a run sees a hundred rules and an instance may hold more, so which hundred is a fairness question rather than a detail. `last_checked_at` answers it: the due query orders on it, and Evaluate advances it for every rule the pass reached — fired, matched nothing, or failed. The watermark cannot do that job, because it moves only on a firing and idle is exactly what keeps it old: ordering on it left the hundred oldest a fixed set, with rule 101 never evaluated on any run, which is F83 and what migration 03100 separates.

**A rule cannot trigger itself, and the mechanism is the watermark.** Every match query orders by (event time, id) and reads the window that opens strictly after the pair `(last_fired_at, last_fired_subject_id)` and closes at `now`; the claim that fires a rule advances both halves past the last subject it handled *before* any action runs. A pair rather than an instant, because a capped fetch can stop between two subjects sharing one timestamp and only a position in the full match order can say which of them were handled — see resumeCursor. A subject is therefore visible to a rule exactly once, whatever the actions did and however many times the scheduler ticks.

**A rule cannot loop through another rule either**, and that is a separate mechanism: no action writes anything any trigger reads. domain.TriggerReads and domain.ActionWrites declare both halves and TestNoAutomationActionWritesATriggerSource asserts they never intersect. It is why the webhook action emits only `automation.fired` — an event nothing triggers on — rather than letting a rule choose. m43.md names the failure as *an automation that fires a webhook that fires an automation*; the answer is that the vocabulary makes the second half unreachable, and that the assertion fails the build if anybody widens it.

Index

Constants

View Source
const SubjectsShown = 5

SubjectsShown is how many subjects a notification and a webhook payload name.

Five. A firing that matched twenty-five links should produce one inbox item somebody reads, not a wall of aliases; the count travels beside the list so a truncated list never reads as a complete one.

Variables

This section is empty.

Functions

This section is empty.

Types

type Archiver

type Archiver interface {
	ArchiveByRule(ctx context.Context, workspaceID, linkID uuid.UUID) (bool, error)
}

Archiver is internal/link's archive, as this package needs it.

Declared here rather than imported so the graph stays one-way: internal/link never imports this package, exactly as it never imports internal/webhook. The method takes no actor because there is none — see internal/link/automation.go for why a synthetic identity would have been the worse answer.

type Config

type Config struct {
	Links    Archiver
	Notifier Notifier
	Events   Emitter
	Audit    audit.Recorder
	Logger   *slog.Logger
	Observer Observer
	// Now overrides the clock. Nil takes time.Now.
	Now func() time.Time
}

Config is what a Service needs. Its own struct rather than config.Config, matching every other service in this tree: the package that does the work does not read the environment.

type Emitter

type Emitter interface {
	Emit(ctx context.Context, workspaceID uuid.UUID, event string, data map[string]any)
}

Emitter is internal/webhook's writing half, as internal/link already declares it. Nil emits nothing.

type Notifier

type Notifier interface {
	AutomationFired(ctx context.Context, orgID, workspaceID, ruleID uuid.UUID,
		ruleName, trigger string, matched int, subjects []string) error
}

Notifier is internal/notify's automation half. Nil notifies nobody, which is what a process built without a notifier gets.

type Observer

type Observer interface {
	ObserveAutomationFiring(trigger, outcome string)
}

Observer counts firings. Nil counts nothing.

The label vocabulary is bounded by construction: three triggers and two outcomes, so the whole metric is six series however many rules exist. M13's cardinality rule is what makes a rule name unacceptable as a label.

type Service

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

Service is the evaluator.

func NewService

func NewService(pool *pgxpool.Pool, cfg Config) *Service

func (*Service) Evaluate

func (s *Service) Evaluate(ctx context.Context) error

Evaluate runs one pass over the due rules, and is what the scheduler calls.

One rule's failure never stops the pass: errors are collected and the remaining rules are still evaluated, because one workspace's broken rule must not stop everybody else's.

Jump to

Keyboard shortcuts

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