events: HTTP receiver, CLI wiring, conductor dispatch stub

`harness events` serves POST /hooks/{redmine,discourse,gitea} and
GET /healthz on stdlib net/http until SIGINT/SIGTERM (graceful
shutdown). Flow per delivery: verify (401, generic body) -> normalize
(400) -> append-only store with dedup (200 stored/duplicate + id +
action) -> hand stored actionable events to the conductor via the
Dispatcher interface. Conductor.DispatchEvent is the wiring point and
prints what it will do once phase 3 lands the real event-to-turn
dispatch. Body cap 1 MiB (413); audit log carries normalized fields +
digest only, never headers, secrets or payload. Server refuses to start
without at least one resolvable webhook secret.
This commit is contained in:
2026-08-28 21:38:24 -05:00
parent 05ec1a4142
commit 043e03b830
4 changed files with 622 additions and 1 deletions
+11
View File
@@ -12,6 +12,7 @@ import (
"time"
"git.knownelement.com/reachableceo/MOPAC/harness/internal/config"
"git.knownelement.com/reachableceo/MOPAC/harness/internal/events"
"git.knownelement.com/reachableceo/MOPAC/harness/internal/intake"
"git.knownelement.com/reachableceo/MOPAC/harness/internal/llm"
"git.knownelement.com/reachableceo/MOPAC/harness/internal/models"
@@ -309,3 +310,13 @@ REPORT. Use the bash tool only when necessary; commands run in %s and must
match the configured allow-list (unmatched or denied commands fail).`,
c.cfg.Vertical, c.cfg.WorkRoot)
}
// DispatchEvent is the events -> conductor wiring point: the `harness
// events` receiver hands every stored, actionable webhook to the conductor
// here. Phase 2b stub: it records what a real dispatch would do; the
// event -> turn wiring (dispatch/redirect for Redmine, context update +
// response for Discourse, pipeline step for Gitea) lands in phase 3.
func (c *Conductor) DispatchEvent(ctx context.Context, ev events.Event) {
fmt.Fprintf(c.out, "harness: stub: dispatch %s for %s (source=%s kind=%s actor=%s provider_id=%s) - turn wiring lands in phase 3\n",
ev.Action, ev.SubjectID, ev.Source, ev.Kind, ev.Actor, ev.ProviderID)
}