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 ¶
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 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 ¶
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.
type ExecExitError ¶
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 ¶
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.
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 ¶
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.