event

package
v0.14.0 Latest Latest
Warning

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

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

Documentation

Overview

Package event owns the shared event-dispatch plumbing for the `specscore` CLI: the Subscriber extension point, the Event envelope type, the envelope validator, the fan-out dispatcher, the built-in subscriber implementations (JsonlWriter, NoOp, Exec), and the events: config block loader.

See `spec/features/cli/event/README.md` for the full Feature contract.

This file currently scopes the package to the Subscriber interface and the Event/Actor/Artifact envelope types. Subscribers, validator, dispatcher, and config loader land in follow-on tasks.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Validate

func Validate(e Event) error

Validate checks an Event against REQ:envelope-validation. It is pure: no I/O, no clock access. On a valid envelope it returns nil; otherwise it returns a *ValidationError naming the first failing field and the rule violated. Payload field-level inspection is explicitly out — the function only confirms the payload bytes parse as a JSON object.

Types

type Actor

type Actor struct {
	Kind string `json:"kind"`
	ID   string `json:"id"`
}

Actor identifies the originator of an event.

type Artifact

type Artifact struct {
	Type     string `json:"type"`
	ID       string `json:"id"`
	Path     string `json:"path"`
	Revision string `json:"revision"`
}

Artifact identifies the SpecScore artifact an event refers to.

type DispatchResult

type DispatchResult struct {
	// ValidationError is non-nil when envelope validation failed; in that
	// case no subscriber was invoked.
	ValidationError error
	// Delivered counts subscribers whose Deliver returned nil.
	Delivered int
	// Failed counts subscribers whose Deliver returned a non-nil error.
	Failed int
	// Failures lists each non-nil Deliver error in declared order.
	Failures []SubscriberFailure
}

DispatchResult is the structured outcome of a Dispatch call. The verb layer maps it to the standard exit-code contract (REQ:dispatch-exit-codes):

  • ValidationError != nil -> exit 2
  • Delivered > 0 OR len(subscribers) == 0 -> exit 0
  • Failed > 0 AND Delivered == 0 AND len(subscribers) > 0 -> exit 10

Exit codes 3 (missing project root) and other 2-class failures (config validation) are produced by callers above Dispatch.

func Dispatch

func Dispatch(ctx context.Context, e Event, subscribers []Subscriber) DispatchResult

Dispatch fans an envelope out to subscribers sequentially in declared order. It first validates the envelope; on validation failure it returns immediately with ValidationError populated and no subscriber invoked. On a valid envelope it calls each subscriber's Deliver; per-subscriber errors are logged to stderr in the contracted key=value form and the iteration continues to the next subscriber. Successful deliveries produce no stderr output (REQ:fan-out-dispatch).

The failure line format is:

event-dispatch failure: subscriber=<Name()> event=<e.Name> error="<err.Error()>"

Embedded double quotes and backslashes in err.Error() are escaped so the line round-trips through standard log parsers.

type Event

type Event struct {
	Name      string          `json:"name"`
	Version   int             `json:"version"`
	UUID      string          `json:"uuid"`
	Timestamp time.Time       `json:"timestamp"`
	Actor     Actor           `json:"actor"`
	Artifact  Artifact        `json:"artifact"`
	Payload   json.RawMessage `json:"payload"`
}

Event is the common envelope passed to every Subscriber. Field shapes mirror the cross-repo event contract; see REQ:envelope-validation in spec/features/cli/event/README.md.

type Exec

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

Exec is a Subscriber that delivers events by spawning a child process and piping the JSON-serialized envelope to its stdin. It enforces a wall-clock timeout: on expiry the child receives SIGTERM, then SIGKILL after a 100 ms grace window. The configured env mapping is appended to the inherited process environment (additive, not replacement).

func NewExec

func NewExec(argv []string, env map[string]string, timeout time.Duration) *Exec

NewExec constructs an Exec subscriber. argv[0] is the executable and argv[1:] are positional arguments. env may be nil. timeout is the wall-clock budget for the child; the config-loader (task 6) enforces the [100, 30000] ms bounds — this constructor does not.

func (*Exec) Deliver

func (x *Exec) Deliver(ctx context.Context, e Event) error

Deliver runs the configured command with the event JSON piped to stdin. Returns *ExecTimeoutError on wall-clock timeout, *ExecExitError on non-zero exit, or a plain error for setup failures (serialization, pipe creation).

func (*Exec) Name

func (x *Exec) Name() string

Name returns "exec:<argv[0]>" so the dispatcher's stderr failure log can identify which exec subscriber failed.

type ExecExitError

type ExecExitError struct {
	ExitCode int
	Cause    error
}

ExecExitError is returned when the child exited non-zero without hitting the timeout. ExitCode is the OS-reported exit status.

func (*ExecExitError) Error

func (e *ExecExitError) Error() string

func (*ExecExitError) Unwrap

func (e *ExecExitError) Unwrap() error

type ExecTimeoutError

type ExecTimeoutError struct {
	Timeout time.Duration
	Cause   error
}

ExecTimeoutError is returned when the configured wall-clock timeout elapsed before the child completed. It is distinguishable from *ExecExitError so the dispatcher's stderr log can name the failure mode.

func (*ExecTimeoutError) Error

func (e *ExecTimeoutError) Error() string

func (*ExecTimeoutError) Unwrap

func (e *ExecTimeoutError) Unwrap() error

type JsonlWriter

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

JsonlWriter is a Subscriber that appends each delivered Event as a single JSON line to a file. It is the default subscriber synthesized by the config loader when the `events:` block is omitted; see spec/features/cli/event/README.md.

Relative configured paths resolve against the project root supplied at construction time, NEVER against the current working directory. The dispatcher wires the project root in once, at startup.

func NewJsonlWriter

func NewJsonlWriter(path string, projectRoot string) *JsonlWriter

NewJsonlWriter constructs a JsonlWriter that will append to `path`. When `path` is relative it is joined against `projectRoot`; when it is absolute it is used as-is. The resolved path is cached on the struct so Deliver and Name remain stable across calls.

func (*JsonlWriter) Deliver

func (w *JsonlWriter) Deliver(_ context.Context, e Event) error

Deliver serializes e to single-line JSON and appends it (followed by a single newline) to the configured file. Parent directories are created at mode 0755 if absent; the file itself is opened with O_APPEND|O_CREATE |O_WRONLY at mode 0644.

func (*JsonlWriter) Name

func (w *JsonlWriter) Name() string

Name returns "jsonl:<resolved-path>" — using the resolved absolute path so failure logs point at the actual file on disk.

type NoOp

type NoOp struct{}

NoOp is the explicit-opt-out Subscriber: it accepts every event, performs no work, and returns nil. It exists so an operator can configure events: with a "noop" entry and signal "I have considered subscribers and chosen none" rather than relying on the absence of configuration. See AC:noop-discards in spec/features/cli/event/README.md.

func (NoOp) Deliver

func (NoOp) Deliver(ctx context.Context, e Event) error

Deliver discards the event and returns nil. The implementation MUST NOT touch the filesystem, network, stdout, or stderr.

func (NoOp) Name

func (NoOp) Name() string

Name returns the literal identifier "noop".

type Subscriber

type Subscriber interface {
	// Deliver is invoked by the dispatcher with a validated envelope. It
	// returns nil on successful delivery and a non-nil error on any failure
	// (timeout, exec exit non-zero, filesystem error, etc.).
	Deliver(ctx context.Context, e Event) error

	// Name returns a stable identifier used in stderr failure logs. The
	// dispatcher does not interpret the string.
	Name() string
}

Subscriber is the extension point for receiving dispatched events. Any type implementing Subscriber may be registered via the events: config block in specscore.yaml. Implementations MUST be safe to call repeatedly within a single CLI invocation.

func LoadSubscribers

func LoadSubscribers(projectRoot string) ([]Subscriber, error)

LoadSubscribers parses <projectRoot>/specscore.yaml and returns the configured Subscriber list. When the file does not exist OR exists but does not contain an `events:` key, the default JsonlWriter at `.specscore/events.jsonl` is synthesized. An explicit `events: {subscribers: []}` is honored as the zero-subscriber list (no synthesis).

type SubscriberFailure

type SubscriberFailure struct {
	Name string
	Err  error
}

SubscriberFailure pairs a failing subscriber's Name() with the error its Deliver returned. The dispatcher emits one stderr line per entry; the verb layer additionally inspects the slice when mapping to exit codes.

type ValidationError

type ValidationError struct {
	// Field is the dotted JSON path of the offending field (e.g. "name",
	// "actor.kind", "artifact.type").
	Field string
	// Rule is a human-readable description of the rule that was violated;
	// regex rules embed the pattern verbatim so callers can grep for it.
	Rule string
	// Value is the offending value rendered for stderr. Empty when the
	// rule is "must not be empty" and the field is a string.
	Value string
}

ValidationError is the deterministic error returned by Validate. The dispatcher prints its Error() string verbatim to stderr; the envelope-validation ACs assert that the message names both the offending Field and the Rule violated, so both elements MUST appear in the formatted string.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error formats the validation error as `envelope validation failed: field=<f> value=<v> rule=<r>`. The shape is stable so tests can string-match on it.

Jump to

Keyboard shortcuts

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