Skip to content
quarry Artifact [d4b109c108]
Login

Artifact [d4b109c108]

Artifact d4b109c108ee965a8a7ad8256cae30d8112005c34c72545556ee533dba6486a5:


package events

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/nats-io/nats.go"

	"craftdesign.group/quarry/internal/evt"
)

// VocabStream carries RFC 0005's forge vocabulary: quarry.evt.<repo>.<noun>.<action>.
//
// It is deliberately NOT the ingest stream. ZITEVT is quarry's own
// plumbing — parsed artifacts on a one-hour window, consumed within
// seconds by the local folder — and its consumer subscribes to
// `zit.evt.<repo>.>`, which would swallow a vocabulary event and try to
// fold it as an artifact. These events have a different audience (dagnats
// workflows, webhooks, anything subscribing from outside) and so a
// different lifetime.
const (
	VocabStream  = "QUARRYEVT"
	vocabSubject = "quarry.evt.>"
)

// vocabConfig is Limits retention, not WorkQueue: every subscriber gets
// its own copy. A workflow reacting to landing.opened must not consume
// the event out from under a webhook subscription to the same subject —
// which is exactly what WorkQueue would do.
var vocabConfig = &nats.StreamConfig{
	Name:      VocabStream,
	Subjects:  []string{vocabSubject},
	Retention: nats.LimitsPolicy,
	Discard:   nats.DiscardOld,
	MaxBytes:  256 << 20,
	MaxAge:    24 * time.Hour,
}

// Raise publishes one vocabulary event. The payload is whatever the
// subscriber needs to act; the SUBJECT is what it selects on, so a
// subscriber that only wants to know a thing happened never has to open
// the body.
//
// Publishing is best-effort from the caller's point of view — a raised
// event is a notification, never the record of what happened. zit's DAG
// is that (0001 §2), so a dropped event costs a missed reaction, not
// truth. Callers log and continue.
func (s *Server) Raise(kind evt.Kind, repo string, payload map[string]any) error {
	if s == nil {
		return nil
	}
	if !kind.Valid() {
		return fmt.Errorf("events: %q is not in the vocabulary", kind)
	}
	if repo == "" {
		return fmt.Errorf("events: %s needs a repo (the subject carries it)", kind)
	}
	body, err := json.Marshal(map[string]any{
		"kind":    string(kind),
		"repo":    repo,
		"payload": payload,
	})
	if err != nil {
		return fmt.Errorf("events: marshal %s: %w", kind, err)
	}
	if _, err := s.js.Publish(kind.Subject(repo), body); err != nil {
		return fmt.Errorf("events: publish %s: %w", kind, err)
	}
	return nil
}