feat(dispatch): commit-marker gate — # tinqs-marker: <regex> in a workflow requires a matching pushed commit message

Opt-in per workflow via a YAML comment; no comment = unchanged behaviour for
every repo. Marker-less pushes spawn no runner at all (no run queue, no
waste). Added for ariki-game sim-publish-staging (Ozan: deploys are explicit).

Co-Authored-By: Ozan Bozkurt <ozan@tinqs.com>
Co-Authored-By: ZCode GLM-5.3-Flash <agents@tinqs.com>
This commit is contained in:
ozanandZCode GLM-5.3-Flash committed 2026-09-22 18:18:58 +01:00
1 parent 33f967e42e
commit 176c2031c7
1 file changed
+41 -2
+41 -2
View File
@@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"regexp"
"os" "os"
"strings" "strings"
"time" "time"
@@ -233,6 +234,10 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
push.Repository.FullName, branch, push.Pusher.Login, len(push.Commits)) push.Repository.FullName, branch, push.Pusher.Login, len(push.Commits))
changedFiles := collectChangedFiles(push.Commits) changedFiles := collectChangedFiles(push.Commits)
var commitMessages []string
for _, c := range push.Commits {
commitMessages = append(commitMessages, c.Message)
}
workflows, err := fetchWorkflows(c.GiteaURL, c.GiteaToken, push.Repository.FullName, branch) workflows, err := fetchWorkflows(c.GiteaURL, c.GiteaToken, push.Repository.FullName, branch)
if err != nil || len(workflows) == 0 { if err != nil || len(workflows) == 0 {
@@ -247,7 +252,7 @@ func handler(ctx context.Context, request events.APIGatewayProxyRequest) (events
continue continue
} }
if !shouldTrigger(wf, branch, changedFiles) { if !shouldTrigger(wf, content, branch, changedFiles, commitMessages) {
log.Printf("Skipping %s (no matching trigger)", name) log.Printf("Skipping %s (no matching trigger)", name)
continue continue
} }
@@ -633,7 +638,7 @@ func fetchRawFile(baseURL, token, repoFullName, path, branch string) (string, er
// --- Trigger evaluation --- // --- Trigger evaluation ---
func shouldTrigger(wf Workflow, branch string, changedFiles []string) bool { func shouldTrigger(wf Workflow, wfContent string, branch string, changedFiles []string, commitMessages []string) bool {
onMap, ok := wf.On.(map[string]interface{}) onMap, ok := wf.On.(map[string]interface{})
if !ok { if !ok {
return true return true
@@ -701,9 +706,43 @@ func shouldTrigger(wf Workflow, branch string, changedFiles []string) bool {
} }
} }
// Opt-in commit-marker gate (Ozan 2026-09-22): a `# tinqs-marker: <regex>` comment in the
// workflow makes push triggers require a pushed commit whose MESSAGE matches the regex
// (case-insensitive). No match → no runner, nothing spawned. workflow_dispatch events
// never reach this function, so button/CLI publishes are unaffected.
if mr := markerRegex(wfContent); mr != nil {
matched := false
for _, msg := range commitMessages {
if mr.MatchString(msg) {
matched = true
break
}
}
if !matched {
log.Printf("Marker gate: no pushed commit matches %s — not spawning", mr)
return false
}
}
return true return true
} }
// markerRegex extracts the opt-in marker regex from a `# tinqs-marker: <regex>` comment
// in the workflow YAML. Empty (no comment) = no marker gating — behaviour unchanged.
func markerRegex(content string) *regexp.Regexp {
re := regexp.MustCompile(`(?im)^\s*#\s*tinqs-marker:\s*(.+)$`)
m := re.FindStringSubmatch(content)
if len(m) < 2 {
return nil
}
cr, err := regexp.Compile(strings.TrimSpace(m[1]))
if err != nil {
log.Printf("Bad tinqs-marker regex %q: %v", m[1], err)
return nil
}
return cr
}
func matchPath(pattern, file string) bool { func matchPath(pattern, file string) bool {
if strings.HasSuffix(pattern, "/**") { if strings.HasSuffix(pattern, "/**") {
prefix := strings.TrimSuffix(pattern, "/**") prefix := strings.TrimSuffix(pattern, "/**")