// Package events is the `harness events` webhook receiver (DESIGN "Events // are V1 scope"): Redmine, Discourse and Gitea webhooks land here, are // verified against per-source secrets, normalized into one internal Event // record, persisted append-only (JSONL, dedup by provider event id), and // mapped to a conductor action. Secret material is never logged and never // persisted; only payload digests are. package events import "time" // Provider sources. const ( SourceRedmine = "redmine" SourceDiscourse = "discourse" SourceGitea = "gitea" ) // Actions an event can map to (DESIGN trigger -> action semantics). const ( ActionDispatchTurn = "dispatch_turn" // redmine issue update/note -> run a turn for that stack ActionRespondTurn = "respond_turn" // discourse post reply -> context update + response turn ActionPipelineStep = "pipeline_step" // gitea PR approved/merged -> next pipeline step ActionIgnore = "ignore" // recognized but not actionable ) // Event is the normalized internal record every provider webhook becomes. // One shape, three sources: source + kind say what happened, actor who did // it, SubjectID/Subject what it happened to (issue / topic / PR). type Event struct { Source string `json:"source"` Kind string `json:"kind"` // Action is the mapped conductor action (dispatch_turn, ...). Action string `json:"action"` // Actor is the provider-side user who triggered the event. Actor string `json:"actor"` // SubjectID canonically identifies the subject: redmine:issue:42, // discourse:topic:7, gitea:pr:ukrrs/MOPAC#5. SubjectID string `json:"subject_id"` // Subject is the human-readable title (best effort). Subject string `json:"subject,omitempty"` // Repo is the gitea repository full_name when applicable. Repo string `json:"repo,omitempty"` // ProviderID is the provider's own event id (delivery/event header, // falling back to the payload digest) and the dedup identity. ProviderID string `json:"provider_id"` PayloadDigest string `json:"payload_digest"` ReceivedAt time.Time `json:"received_at"` } // DedupKey is the append-store identity: source + provider event id. func (e Event) DedupKey() string { return e.Source + ":" + e.ProviderID } // Sources lists the receiver's provider sources. func Sources() []string { return []string{SourceRedmine, SourceDiscourse, SourceGitea} }