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
}