| 1 | // SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | |
| 3 | // Package event builds canonical `shithub.event` payloads from |
| 4 | // triggering domain events. |
| 5 | // |
| 6 | // The payload schema is the v1 contract documented in |
| 7 | // docs/internal/actions-schema.md. Workflow authors template against |
| 8 | // these field paths from `${{ shithub.event.X }}` (and the `github.event` |
| 9 | // alias) — once data is in `workflow_runs.event_payload`, schema |
| 10 | // changes are breaking. **Adding a field requires a reviewer-required |
| 11 | // note + a doc update.** Treat this package the same way we treat |
| 12 | // migration files. |
| 13 | // |
| 14 | // Each constructor takes typed inputs (so adding a field is visible |
| 15 | // in the function signature) and returns a `map[string]any` because |
| 16 | // that's exactly what `internal/actions/expr.evalEventPath` consumes. |
| 17 | // The returned map is also pgx-encodable to jsonb without further |
| 18 | // transformation, so callers (S41b's trigger pipeline) can pass it |
| 19 | // straight into `InsertWorkflowRun`. |
| 20 | // |
| 21 | // What's NOT here in v1: `workflow_run`-event payloads (re-runs), |
| 22 | // `release` events, `deployment_status`, anything outside the four |
| 23 | // triggers documented in actions-schema.md. Those land in v2 with |
| 24 | // their own constructors. |
| 25 | package event |
| 26 | |
| 27 | // HeadCommit is the typed input shape for push-event head_commit. |
| 28 | // Fields mirror the documented v1 schema: message, id, author. |
| 29 | type HeadCommit struct { |
| 30 | Message string |
| 31 | ID string |
| 32 | Author string |
| 33 | } |
| 34 | |
| 35 | // Push builds the payload for a push trigger. |
| 36 | // |
| 37 | // Documented v1 keys: ref, before, after, head_commit{message,id,author}. |
| 38 | // The author field is a flat string (commit author name) in v1; if a |
| 39 | // future field needs the full author object, that's a v2 break. |
| 40 | func Push(ref, before, after string, hc HeadCommit) map[string]any { |
| 41 | return map[string]any{ |
| 42 | "ref": ref, |
| 43 | "before": before, |
| 44 | "after": after, |
| 45 | "head_commit": map[string]any{ |
| 46 | "message": hc.Message, |
| 47 | "id": hc.ID, |
| 48 | "author": hc.Author, |
| 49 | }, |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | // PRRef is the head/base ref descriptor inside a pull_request payload. |
| 54 | type PRRef struct { |
| 55 | Ref string // e.g. "feature/foo" or "main" |
| 56 | SHA string // 40-char object id |
| 57 | } |
| 58 | |
| 59 | // PullRequest builds the payload for a pull_request trigger. |
| 60 | // |
| 61 | // Documented v1 keys: |
| 62 | // |
| 63 | // action, number, |
| 64 | // pull_request{title, head{ref,sha}, base{ref,sha}, user{login}}. |
| 65 | // |
| 66 | // The action field tracks the activity type ("opened", "synchronize", |
| 67 | // "reopened", "closed", "edited", "labeled", "unlabeled"). v1 doesn't |
| 68 | // constrain the value here — S41b's pull_request emitter is the |
| 69 | // allowlist. |
| 70 | func PullRequest(action string, number int64, title string, head, base PRRef, userLogin string) map[string]any { |
| 71 | return map[string]any{ |
| 72 | "action": action, |
| 73 | "number": number, |
| 74 | "pull_request": map[string]any{ |
| 75 | "title": title, |
| 76 | "head": map[string]any{ |
| 77 | "ref": head.Ref, |
| 78 | "sha": head.SHA, |
| 79 | }, |
| 80 | "base": map[string]any{ |
| 81 | "ref": base.Ref, |
| 82 | "sha": base.SHA, |
| 83 | }, |
| 84 | "user": map[string]any{ |
| 85 | "login": userLogin, |
| 86 | }, |
| 87 | }, |
| 88 | } |
| 89 | } |
| 90 | |
| 91 | // Schedule is the payload for a cron-fired schedule trigger. Empty |
| 92 | // by design — the cron expression itself is on the workflow_runs row, |
| 93 | // not in the payload. Returned as a non-nil empty map so calling code |
| 94 | // can pgx-encode without a nil check. |
| 95 | func Schedule() map[string]any { |
| 96 | return map[string]any{} |
| 97 | } |
| 98 | |
| 99 | // WorkflowDispatch builds the payload for a manual workflow_dispatch |
| 100 | // trigger. Inputs are stringified (matching GHA semantics — even |
| 101 | // boolean inputs arrive as "true"/"false" strings) and wrapped under |
| 102 | // the `inputs` key so authors template `${{ shithub.event.inputs.foo }}`. |
| 103 | func WorkflowDispatch(inputs map[string]string) map[string]any { |
| 104 | bag := make(map[string]any, len(inputs)) |
| 105 | for k, v := range inputs { |
| 106 | bag[k] = v |
| 107 | } |
| 108 | return map[string]any{ |
| 109 | "inputs": bag, |
| 110 | } |
| 111 | } |
| 112 |