ci: trigger unity and cuelang.org builds/equivalent on tip/new versions

A new tip merge of CUE should:

* trigger a unity build
* trigger a redeploy of tip.cuelang.org

A new release of CUE should:

* trigger a unity build against that version
* prompt us to release a new version of cuelang.org

We could choose to make the release of a new version of cuelang.org on a
new version of CUE automatic (or automate the creation of a PR that
releases a new version of cuelang.org), but for now we simply trigger a
build which will fail if the current version of CUE referenced by
cuelang.org is _not_ the latest. This in effect will be the prompt we
need to perform a manual release of cuelang.org, using the latest CUE.

This change reflects the desired end state described above. Automating
the release of cuelang.org/creation of a PR can follow later.

Change-Id: I6623fd50c6c15e4f423bc204d65e3dc98569da7a
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9576
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Paul Jolly <paul@myitcv.org.uk>
diff --git a/.github/workflows/new_version_triggers.yml b/.github/workflows/new_version_triggers.yml
new file mode 100644
index 0000000..a745e14
--- /dev/null
+++ b/.github/workflows/new_version_triggers.yml
@@ -0,0 +1,23 @@
+# Generated by internal/ci/ci_tool.cue; do not edit
+
+name: New release triggers
+on:
+  push:
+    tags:
+    - v*
+jobs:
+  push:
+    runs-on: ubuntu-18.04
+    defaults:
+      run:
+        shell: bash
+    steps:
+    - name: Rebuild tip.cuelang.org
+      run: 'curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT
+        --request POST --data-binary "{"event_type": "Re-test post release of ${GITHUB_REF##refs/tags/}"}"
+        https://api.github.com/repos/cuelang/cuelang.org/dispatches'
+    - name: Trigger unity build
+      run: 'curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT
+        --request POST --data-binary "{"event_type": "Check against CUE ${GITHUB_REF##refs/tags/}",
+        "client_payload": {"type": "unity", "payload": {"versions": "\"${GITHUB_REF##refs/tags/}}})\""}}}"
+        https://api.github.com/repos/cue-sh/unity/dispatches'
diff --git a/.github/workflows/rebuild_tip_cuelang_org.yml b/.github/workflows/rebuild_tip_cuelang_org.yml
deleted file mode 100644
index 3e5651b..0000000
--- a/.github/workflows/rebuild_tip_cuelang_org.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-# Generated by internal/ci/ci_tool.cue; do not edit
-
-name: Push to tip
-on:
-  push:
-    branches:
-    - master
-jobs:
-  push:
-    runs-on: ubuntu-18.04
-    defaults:
-      run:
-        shell: bash
-    steps:
-    - name: Rebuild tip.cuelang.org
-      run: curl -f -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.CuelangOrgTipRebuildHook
-        }}
diff --git a/.github/workflows/tip_triggers.yml b/.github/workflows/tip_triggers.yml
new file mode 100644
index 0000000..994975a
--- /dev/null
+++ b/.github/workflows/tip_triggers.yml
@@ -0,0 +1,22 @@
+# Generated by internal/ci/ci_tool.cue; do not edit
+
+name: Push to tip triggers
+on:
+  push:
+    branches:
+    - master
+jobs:
+  push:
+    runs-on: ubuntu-18.04
+    defaults:
+      run:
+        shell: bash
+    steps:
+    - name: Rebuild tip.cuelang.org
+      run: curl -f -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.CuelangOrgTipRebuildHook
+        }}
+    - name: Trigger unity build
+      run: 'curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT
+        --request POST --data-binary "{"event_type": "Check against ${GITHUB_SHA}",
+        "client_payload": {"type": "unity", "payload": {"versions": "\"commit:${GITHUB_SHA})\""}}}"
+        https://api.github.com/repos/cue-sh/unity/dispatches'
diff --git a/cmd/cue/cmd/testdata/script/cmd_github.txt b/cmd/cue/cmd/testdata/script/cmd_github.txt
index bf0b908..aedf063 100644
--- a/cmd/cue/cmd/testdata/script/cmd_github.txt
+++ b/cmd/cue/cmd/testdata/script/cmd_github.txt
@@ -11,22 +11,53 @@
 
 # TODO: drop cd when we solve cuelang.org/issue/708
 cd ../../
-cmp .github/workflows/rebuild_tip_cuelang_org.yml .github/workflows/rebuild_tip_cuelang_org.yml.golden
+cmp .github/workflows/mirror.yml .github/workflows/mirror.yml.golden
+cmp .github/workflows/new_version_triggers.yml .github/workflows/new_version_triggers.yml.golden
 cmp .github/workflows/release.yml .github/workflows/release.yml.golden
 cmp .github/workflows/repository_dispatch.yml .github/workflows/repository_dispatch.yml.golden
 cmp .github/workflows/test.yml .github/workflows/test.yml.golden
-cmp .github/workflows/mirror.yml .github/workflows/mirror.yml.golden
+cmp .github/workflows/tip_triggers.yml .github/workflows/tip_triggers.yml.golden
 
 -- cue.mod/module --
 module "cuelang.org/go"
--- .github/workflows/rebuild_tip_cuelang_org.yml.golden --
+-- .github/workflows/mirror.yml.golden --
 # Generated by internal/ci/ci_tool.cue; do not edit
 
-name: Push to tip
+name: Scheduled repo mirror
+on:
+  schedule:
+  - cron: '*/30 * * * *'
+jobs:
+  mirror:
+    runs-on: ubuntu-18.04
+    defaults:
+      run:
+        shell: bash
+    steps:
+    - name: Checkout code
+      uses: actions/checkout@v2
+    - name: Mirror Gerrit to GitHub
+      run: |-
+        cd _scripts
+        docker run --rm -v $PWD/cache:/root/copybara/cache -v $PWD:/usr/src/app --entrypoint="" cueckoo/copybara:afc4ae03eed00b0c9d7415141cd1b5dfa583da7c bash -c " \
+        	set -eu; \
+        	echo \"${{ secrets.gerritCookie }}\" > ~/.gitcookies; \
+        	chmod 600 ~/.gitcookies; \
+        	git config --global user.name cueckoo; \
+        	git config --global user.email cueckoo@gmail.com; \
+        	git config --global http.cookiefile \$HOME/.gitcookies; \
+          	echo https://cueckoo:${{ secrets.CUECKOO_GITHUB_PAT }}@github.com > ~/.git-credentials; \
+        	chmod 600 ~/.git-credentials; \
+        	java -jar /opt/copybara/copybara_deploy.jar migrate copy.bara.sky github; \
+        	"
+-- .github/workflows/new_version_triggers.yml.golden --
+# Generated by internal/ci/ci_tool.cue; do not edit
+
+name: New release triggers
 on:
   push:
-    branches:
-    - master
+    tags:
+    - v*
 jobs:
   push:
     runs-on: ubuntu-18.04
@@ -35,8 +66,14 @@
         shell: bash
     steps:
     - name: Rebuild tip.cuelang.org
-      run: curl -f -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.CuelangOrgTipRebuildHook
-        }}
+      run: 'curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT
+        --request POST --data-binary "{"event_type": "Re-test post release of ${GITHUB_REF##refs/tags/}"}"
+        https://api.github.com/repos/cuelang/cuelang.org/dispatches'
+    - name: Trigger unity build
+      run: 'curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT
+        --request POST --data-binary "{"event_type": "Check against CUE ${GITHUB_REF##refs/tags/}",
+        "client_payload": {"type": "unity", "payload": {"versions": "\"${GITHUB_REF##refs/tags/}}})\""}}}"
+        https://api.github.com/repos/cue-sh/unity/dispatches'
 -- .github/workflows/release.yml.golden --
 # Generated by internal/ci/ci_tool.cue; do not edit
 
@@ -293,36 +330,171 @@
         git config user.email cueckoo@gmail.com
         git config http.https://github.com/.extraheader "AUTHORIZATION: basic $(echo -n cueckoo:${{ secrets.CUECKOO_GITHUB_PAT }} | base64)"
         git push https://github.com/cuelang/cue :${GITHUB_REF#refs/heads/}
--- .github/workflows/mirror.yml.golden --
+-- .github/workflows/tip_triggers.yml.golden --
 # Generated by internal/ci/ci_tool.cue; do not edit
 
-name: Scheduled repo mirror
+name: Push to tip triggers
 on:
-  schedule:
-  - cron: '*/30 * * * *'
+  push:
+    branches:
+    - master
 jobs:
-  mirror:
+  push:
     runs-on: ubuntu-18.04
     defaults:
       run:
         shell: bash
     steps:
-    - name: Checkout code
-      uses: actions/checkout@v2
-    - name: Mirror Gerrit to GitHub
-      run: |-
-        cd _scripts
-        docker run --rm -v $PWD/cache:/root/copybara/cache -v $PWD:/usr/src/app --entrypoint="" cueckoo/copybara:afc4ae03eed00b0c9d7415141cd1b5dfa583da7c bash -c " \
-        	set -eu; \
-        	echo \"${{ secrets.gerritCookie }}\" > ~/.gitcookies; \
-        	chmod 600 ~/.gitcookies; \
-        	git config --global user.name cueckoo; \
-        	git config --global user.email cueckoo@gmail.com; \
-        	git config --global http.cookiefile \$HOME/.gitcookies; \
-          	echo https://cueckoo:${{ secrets.CUECKOO_GITHUB_PAT }}@github.com > ~/.git-credentials; \
-        	chmod 600 ~/.git-credentials; \
-        	java -jar /opt/copybara/copybara_deploy.jar migrate copy.bara.sky github; \
-        	"
+    - name: Rebuild tip.cuelang.org
+      run: curl -f -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.CuelangOrgTipRebuildHook
+        }}
+    - name: Trigger unity build
+      run: 'curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT
+        --request POST --data-binary "{"event_type": "Check against ${GITHUB_SHA}",
+        "client_payload": {"type": "unity", "payload": {"versions": "\"commit:${GITHUB_SHA})\""}}}"
+        https://api.github.com/repos/cue-sh/unity/dispatches'
+-- internal/ci/ci_tool.cue --
+// Copyright 2021 The CUE Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package ci
+
+import (
+	"path"
+
+	"encoding/yaml"
+
+	"tool/exec"
+	"tool/file"
+	"tool/os"
+)
+
+// genworkflows regenerates the GitHub workflow Yaml definitions.
+//
+// See internal/ci/gen.go for details on how this step fits into the sequence
+// of generating our CI workflow definitions, and updating various txtar tests
+// with files from that process.
+//
+// Until we have a resolution for cuelang.org/issue/704 and
+// cuelang.org/issue/708 this must be run from the internal/ci package. At
+// which point we can switch to using _#modroot.
+//
+// This also explains why the ../../ relative path specification below appear
+// wrong in the context of the containing directory internal/ci/vendor.
+command: genworkflows: {
+	goos: _#goos
+
+	for w in workflows {
+		"\(w.file)": file.Create & {
+			_dir:     path.FromSlash("../../.github/workflows", path.Unix)
+			filename: path.Join([_dir, w.file], goos.GOOS)
+			contents: """
+						 # Generated by internal/ci/ci_tool.cue; do not edit
+
+						 \(yaml.Marshal(w.schema))
+						 """
+		}
+	}
+}
+
+// updateTxtarTests ensures certain txtar tests are updated with the
+// relevant files that make up the process of generating our CI
+// workflows.
+//
+// See internal/ci/gen.go for details on how this step fits into the sequence
+// of generating our CI workflow definitions, and updating various txtar tests
+// with files from that process.
+//
+// Until we have a resolution for cuelang.org/issue/704 and
+// cuelang.org/issue/708 this must be run from the internal/ci package. At
+// which point we can switch to using _#modroot.
+//
+// This also explains why the ../../ relative path specification below appear
+// wrong in the context of the containing directory internal/ci/vendor.
+command: updateTxtarTests: {
+	goos: _#goos
+
+	readJSONSchema: file.Read & {
+		_path:    path.FromSlash("../../cue.mod/pkg/github.com/SchemaStore/schemastore/src/schemas/json/github-workflow.cue", path.Unix)
+		filename: path.Join([_path], goos.GOOS)
+		contents: string
+	}
+	cueDefInternalCI: exec.Run & {
+		cmd:    "go run cuelang.org/go/cmd/cue def cuelang.org/go/internal/ci"
+		stdout: string
+	}
+	// updateEvalTxtarTest updates the cue/testdata/eval testscript which exercises
+	// the evaluation of the workflows defined in internal/ci (which by definition
+	// means resolving and using the vendored GitHub Workflow schema)
+	updateEvalTxtarTest: {
+		_relpath: path.FromSlash("../../cue/testdata/eval/github.txtar", path.Unix)
+		_path:    path.Join([_relpath], goos.GOOS)
+
+		githubSchema: exec.Run & {
+			stdin: readJSONSchema.contents
+			cmd:   "go run cuelang.org/go/internal/ci/updatetxtar - \(_path) cue.mod/pkg/github.com/SchemaStore/schemastore/src/schemas/json/github-workflow.cue"
+		}
+		defWorkflows: exec.Run & {
+			$after: githubSchema
+			stdin:  cueDefInternalCI.stdout
+			cmd:    "go run cuelang.org/go/internal/ci/updatetxtar - \(_path) workflows.cue"
+		}
+	}
+	// When we have a solution for cuelang.org/issue/709 we can make this a
+	// file.Glob. Ultimately it would be better to be able to do a cue def
+	// on the tool "package"
+	readToolsFile: file.Read & {
+		filename: "ci_tool.cue"
+		contents: string
+	}
+	updateCmdCueCmdTxtarTest: {
+		_relpath: path.FromSlash("../../cmd/cue/cmd/testdata/script/cmd_github.txt", path.Unix)
+		_path:    path.Join([_relpath], goos.GOOS)
+
+		githubSchema: exec.Run & {
+			stdin: readJSONSchema.contents
+			cmd:   "go run cuelang.org/go/internal/ci/updatetxtar - \(_path) cue.mod/pkg/github.com/SchemaStore/schemastore/src/schemas/json/github-workflow.cue"
+		}
+		defWorkflows: exec.Run & {
+			$after: githubSchema
+			stdin:  cueDefInternalCI.stdout
+			cmd:    "go run cuelang.org/go/internal/ci/updatetxtar - \(_path) internal/ci/workflows.cue"
+		}
+		toolsFile: exec.Run & {
+			stdin: readToolsFile.contents
+			cmd:   "go run cuelang.org/go/internal/ci/updatetxtar - \(_path) internal/ci/\(readToolsFile.filename)"
+		}
+	}
+}
+
+// _#modroot is a common helper to get the module root
+//
+// TODO: use once we have a solution to cuelang.org/issue/704.
+// This will then allow us to remove the use of .. below.
+_#modroot: exec.Run & {
+	cmd:    "go list -m -f {{.Dir}}"
+	stdout: string
+}
+
+// Until we have the ability to inject contextual information
+// we need to pass in GOOS explicitly. Either by environment
+// variable (which we get for free when this is used via go generate)
+// or via a tag in the case you want to manually run the CUE
+// command.
+_#goos: os.Getenv & {
+	GOOS: *path.Unix | string @tag(os)
+}
 -- cue.mod/pkg/github.com/SchemaStore/schemastore/src/schemas/json/github-workflow.cue --
 package json
 
@@ -987,148 +1159,6 @@
 
 	#: "working-directory": string
 }
--- internal/ci/ci_tool.cue --
-// Copyright 2021 The CUE Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package ci
-
-import (
-	"path"
-
-	"encoding/yaml"
-
-	"tool/exec"
-	"tool/file"
-	"tool/os"
-)
-
-// genworkflows regenerates the GitHub workflow Yaml definitions.
-//
-// See internal/ci/gen.go for details on how this step fits into the sequence
-// of generating our CI workflow definitions, and updating various txtar tests
-// with files from that process.
-//
-// Until we have a resolution for cuelang.org/issue/704 and
-// cuelang.org/issue/708 this must be run from the internal/ci package. At
-// which point we can switch to using _#modroot.
-//
-// This also explains why the ../../ relative path specification below appear
-// wrong in the context of the containing directory internal/ci/vendor.
-command: genworkflows: {
-	goos: _#goos
-
-	for w in workflows {
-		"\(w.file)": file.Create & {
-			_dir:     path.FromSlash("../../.github/workflows", path.Unix)
-			filename: path.Join([_dir, w.file], goos.GOOS)
-			contents: """
-						 # Generated by internal/ci/ci_tool.cue; do not edit
-
-						 \(yaml.Marshal(w.schema))
-						 """
-		}
-	}
-}
-
-// updateTxtarTests ensures certain txtar tests are updated with the
-// relevant files that make up the process of generating our CI
-// workflows.
-//
-// See internal/ci/gen.go for details on how this step fits into the sequence
-// of generating our CI workflow definitions, and updating various txtar tests
-// with files from that process.
-//
-// Until we have a resolution for cuelang.org/issue/704 and
-// cuelang.org/issue/708 this must be run from the internal/ci package. At
-// which point we can switch to using _#modroot.
-//
-// This also explains why the ../../ relative path specification below appear
-// wrong in the context of the containing directory internal/ci/vendor.
-command: updateTxtarTests: {
-	goos: _#goos
-
-	readJSONSchema: file.Read & {
-		_path:    path.FromSlash("../../cue.mod/pkg/github.com/SchemaStore/schemastore/src/schemas/json/github-workflow.cue", path.Unix)
-		filename: path.Join([_path], goos.GOOS)
-		contents: string
-	}
-	cueDefInternalCI: exec.Run & {
-		cmd:    "go run cuelang.org/go/cmd/cue def cuelang.org/go/internal/ci"
-		stdout: string
-	}
-	// updateEvalTxtarTest updates the cue/testdata/eval testscript which exercises
-	// the evaluation of the workflows defined in internal/ci (which by definition
-	// means resolving and using the vendored GitHub Workflow schema)
-	updateEvalTxtarTest: {
-		_relpath: path.FromSlash("../../cue/testdata/eval/github.txtar", path.Unix)
-		_path:    path.Join([_relpath], goos.GOOS)
-
-		githubSchema: exec.Run & {
-			stdin: readJSONSchema.contents
-			cmd:   "go run cuelang.org/go/internal/ci/updatetxtar - \(_path) cue.mod/pkg/github.com/SchemaStore/schemastore/src/schemas/json/github-workflow.cue"
-		}
-		defWorkflows: exec.Run & {
-			$after: githubSchema
-			stdin:  cueDefInternalCI.stdout
-			cmd:    "go run cuelang.org/go/internal/ci/updatetxtar - \(_path) workflows.cue"
-		}
-	}
-	// When we have a solution for cuelang.org/issue/709 we can make this a
-	// file.Glob. Ultimately it would be better to be able to do a cue def
-	// on the tool "package"
-	readToolsFile: file.Read & {
-		filename: "ci_tool.cue"
-		contents: string
-	}
-	updateCmdCueCmdTxtarTest: {
-		_relpath: path.FromSlash("../../cmd/cue/cmd/testdata/script/cmd_github.txt", path.Unix)
-		_path:    path.Join([_relpath], goos.GOOS)
-
-		githubSchema: exec.Run & {
-			stdin: readJSONSchema.contents
-			cmd:   "go run cuelang.org/go/internal/ci/updatetxtar - \(_path) cue.mod/pkg/github.com/SchemaStore/schemastore/src/schemas/json/github-workflow.cue"
-		}
-		defWorkflows: exec.Run & {
-			$after: githubSchema
-			stdin:  cueDefInternalCI.stdout
-			cmd:    "go run cuelang.org/go/internal/ci/updatetxtar - \(_path) internal/ci/workflows.cue"
-		}
-		toolsFile: exec.Run & {
-			stdin: readToolsFile.contents
-			cmd:   "go run cuelang.org/go/internal/ci/updatetxtar - \(_path) internal/ci/\(readToolsFile.filename)"
-		}
-	}
-}
-
-// _#modroot is a common helper to get the module root
-//
-// TODO: use once we have a solution to cuelang.org/issue/704.
-// This will then allow us to remove the use of .. below.
-_#modroot: exec.Run & {
-	cmd:    "go list -m -f {{.Dir}}"
-	stdout: string
-}
-
-// Until we have the ability to inject contextual information
-// we need to pass in GOOS explicitly. Either by environment
-// variable (which we get for free when this is used via go generate)
-// or via a tag in the case you want to manually run the CUE
-// command.
-_#goos: os.Getenv & {
-	GOOS: *path.Unix | string @tag(os)
-}
 -- internal/ci/workflows.cue --
 package ci
 
@@ -1153,8 +1183,11 @@
 	file:   "release.yml"
 	schema: release
 }, {
-	file:   "rebuild_tip_cuelang_org.yml"
-	schema: rebuild_tip_cuelang_org
+	file:   "tip_triggers.yml"
+	schema: tip_triggers
+}, {
+	file:   "new_version_triggers.yml"
+	schema: new_version_triggers
 }, {
 	file:   "mirror.yml"
 	schema: mirror
@@ -1371,14 +1404,37 @@
 		}
 	}
 }
-rebuild_tip_cuelang_org: _#bashWorkflow & {
-	name: "Push to tip"
+tip_triggers: _#bashWorkflow & {
+	name: "Push to tip triggers"
 	on: push: branches: [_#masterBranch]
 	jobs: push: {
 		"runs-on": _#linuxMachine
 		steps: [{
 			name: "Rebuild tip.cuelang.org"
 			run:  "curl -f -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.CuelangOrgTipRebuildHook }}"
+		}, {
+			name: "Trigger unity build"
+			run: """
+				curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary "{\"event_type\": \"Check against ${GITHUB_SHA}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"commit:${GITHUB_SHA})\\\"\"}}}" https://api.github.com/repos/cue-sh/unity/dispatches
+				"""
+		}]
+	}
+}
+new_version_triggers: _#bashWorkflow & {
+	name: "New release triggers"
+	on: push: tags: [_#releaseTagPattern]
+	jobs: push: {
+		"runs-on": _#linuxMachine
+		steps: [{
+			name: "Rebuild tip.cuelang.org"
+			run: """
+				curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary "{\"event_type\": \"Re-test post release of ${GITHUB_REF##refs/tags/}\"}" https://api.github.com/repos/cuelang/cuelang.org/dispatches
+				"""
+		}, {
+			name: "Trigger unity build"
+			run: """
+				curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary "{\"event_type\": \"Check against CUE ${GITHUB_REF##refs/tags/}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"${GITHUB_REF##refs/tags/}}})\\\"\"}}}" https://api.github.com/repos/cue-sh/unity/dispatches
+				"""
 		}]
 	}
 }
diff --git a/cue/testdata/eval/github.txtar b/cue/testdata/eval/github.txtar
index 57adf0f..13f5faa 100644
--- a/cue/testdata/eval/github.txtar
+++ b/cue/testdata/eval/github.txtar
@@ -29,8 +29,11 @@
 	file:   "release.yml"
 	schema: release
 }, {
-	file:   "rebuild_tip_cuelang_org.yml"
-	schema: rebuild_tip_cuelang_org
+	file:   "tip_triggers.yml"
+	schema: tip_triggers
+}, {
+	file:   "new_version_triggers.yml"
+	schema: new_version_triggers
 }, {
 	file:   "mirror.yml"
 	schema: mirror
@@ -247,14 +250,37 @@
 		}
 	}
 }
-rebuild_tip_cuelang_org: _#bashWorkflow & {
-	name: "Push to tip"
+tip_triggers: _#bashWorkflow & {
+	name: "Push to tip triggers"
 	on: push: branches: [_#masterBranch]
 	jobs: push: {
 		"runs-on": _#linuxMachine
 		steps: [{
 			name: "Rebuild tip.cuelang.org"
 			run:  "curl -f -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.CuelangOrgTipRebuildHook }}"
+		}, {
+			name: "Trigger unity build"
+			run: """
+				curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary "{\"event_type\": \"Check against ${GITHUB_SHA}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"commit:${GITHUB_SHA})\\\"\"}}}" https://api.github.com/repos/cue-sh/unity/dispatches
+				"""
+		}]
+	}
+}
+new_version_triggers: _#bashWorkflow & {
+	name: "New release triggers"
+	on: push: tags: [_#releaseTagPattern]
+	jobs: push: {
+		"runs-on": _#linuxMachine
+		steps: [{
+			name: "Rebuild tip.cuelang.org"
+			run: """
+				curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary "{\"event_type\": \"Re-test post release of ${GITHUB_REF##refs/tags/}\"}" https://api.github.com/repos/cuelang/cuelang.org/dispatches
+				"""
+		}, {
+			name: "Trigger unity build"
+			run: """
+				curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary "{\"event_type\": \"Check against CUE ${GITHUB_REF##refs/tags/}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"${GITHUB_REF##refs/tags/}}})\\\"\"}}}" https://api.github.com/repos/cue-sh/unity/dispatches
+				"""
 		}]
 	}
 }
@@ -1284,7 +1310,7 @@
             }
             res: (_|_){
               // [incomplete] invalid interpolation: cannot convert incomplete value "string" to JSON:
-              //     ./workflows.cue:126:9
+              //     ./workflows.cue:129:9
             }
           }
         }
@@ -1408,7 +1434,7 @@
           _#type(:ci): (string){ string }
           if: (_|_){
             // [incomplete] workflows.1.schema._#dispatchJob.if: invalid interpolation: non-concrete value string (type string):
-            //     ./workflows.cue:139:14
+            //     ./workflows.cue:142:14
           }
         }
         name: (string){ "Repository Dispatch" }
@@ -1548,7 +1574,7 @@
       }
     }
     3: (struct){
-      file: (string){ "rebuild_tip_cuelang_org.yml" }
+      file: (string){ "tip_triggers.yml" }
       schema: (#struct){
         #architecture: (string){ |((string){ "ARM32" }, (string){ "x64" }, (string){ "x86" }) }
         #branch: (list){
@@ -1596,6 +1622,10 @@
                 name: (string){ "Rebuild tip.cuelang.org" }
                 run: (string){ "curl -f -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.CuelangOrgTipRebuildHook }}" }
               }
+              1: (#struct){
+                name: (string){ "Trigger unity build" }
+                run: (string){ "curl -s -H \"Content-Type: application/json\" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary \"{\"event_type\": \"Check against ${GITHUB_SHA}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"commit:${GITHUB_SHA})\\\"\"}}}\" https://api.github.com/repos/cue-sh/unity/dispatches" }
+              }
             }
             defaults: (#struct){
               run: (#struct){
@@ -1604,7 +1634,7 @@
             }
           }
         }
-        name: (string){ "Push to tip" }
+        name: (string){ "Push to tip triggers" }
         on: (#struct){
           push: (#struct){
             branches: (#list){
@@ -1615,6 +1645,77 @@
       }
     }
     4: (struct){
+      file: (string){ "new_version_triggers.yml" }
+      schema: (#struct){
+        #architecture: (string){ |((string){ "ARM32" }, (string){ "x64" }, (string){ "x86" }) }
+        #branch: (list){
+          0: (string){ strings.MinRunes(1) }
+        }
+        #configuration: ((bool|string|list|struct|number)){ |((string){ string }, (number){ number }, (bool){ bool }, (#struct){
+          }, (list){
+          }) }
+        #container: (#struct){
+          image: (string){ string }
+        }
+        #defaults: (#struct){
+        }
+        #env: (#struct){
+        }
+        #environment: (#struct){
+          name: (string){ string }
+        }
+        #event: (string){ |((string){ "check_run" }, (string){ "check_suite" }, (string){ "create" }, (string){ "delete" }, (string){ "deployment" }, (string){ "deployment_status" }, (string){ "fork" }, (string){ "gollum" }, (string){ "issue_comment" }, (string){ "issues" }, (string){ "label" }, (string){ "member" }, (string){ "milestone" }, (string){ "page_build" }, (string){ "project" }, (string){ "project_card" }, (string){ "project_column" }, (string){ "public" }, (string){ "pull_request" }, (string){ "pull_request_review" }, (string){ "pull_request_review_comment" }, (string){ "pull_request_target" }, (string){ "push" }, (string){ "registry_package" }, (string){ "release" }, (string){ "status" }, (string){ "watch" }, (string){ "workflow_dispatch" }, (string){ "workflow_run" }, (string){ "repository_dispatch" }) }
+        #eventObject: ((null|struct)){ |((null){ null }, (#struct){
+          }) }
+        #expressionSyntax: (string){ =~"^\\$\\{\\{.*\\}\\}$" }
+        #globs: (list){
+          0: (string){ strings.MinRunes(1) }
+        }
+        #machine: (string){ |((string){ "linux" }, (string){ "macos" }, (string){ "windows" }) }
+        #name: (string){ =~"^[_a-zA-Z][a-zA-Z0-9_-]*$" }
+        #path: (list){
+          0: (string){ strings.MinRunes(1) }
+        }
+        #ref: ((null|struct)){ |((null){ null }, (#struct){
+          }) }
+        #shell: (string){ |((string){ string }, (string){ "bash" }, (string){ "pwsh" }, (string){ "python" }, (string){ "sh" }, (string){ "cmd" }, (string){ "powershell" }) }
+        #types: (list){
+          0: (_){ _ }
+        }
+        #: (#struct){
+          "working-directory": (string){ string }
+        }
+        jobs: (#struct){
+          push: (#struct){
+            "runs-on": (string){ "ubuntu-18.04" }
+            steps: (#list){
+              0: (#struct){
+                name: (string){ "Rebuild tip.cuelang.org" }
+                run: (string){ "curl -s -H \"Content-Type: application/json\" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary \"{\"event_type\": \"Re-test post release of ${GITHUB_REF##refs/tags/}\"}\" https://api.github.com/repos/cuelang/cuelang.org/dispatches" }
+              }
+              1: (#struct){
+                name: (string){ "Trigger unity build" }
+                run: (string){ "curl -s -H \"Content-Type: application/json\" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary \"{\"event_type\": \"Check against CUE ${GITHUB_REF##refs/tags/}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"${GITHUB_REF##refs/tags/}}})\\\"\"}}}\" https://api.github.com/repos/cue-sh/unity/dispatches" }
+              }
+            }
+            defaults: (#struct){
+              run: (#struct){
+                shell: (string){ "bash" }
+              }
+            }
+          }
+        }
+        name: (string){ "New release triggers" }
+        on: (#struct){
+          push: (#struct){
+            tags: (#list){
+              0: (string){ "v*" }
+            }
+          }
+        }
+      }
+    }
+    5: (struct){
       file: (string){ "mirror.yml" }
       schema: (#struct){
         #architecture: (string){ |((string){ "ARM32" }, (string){ "x64" }, (string){ "x86" }) }
@@ -1915,7 +2016,7 @@
         }
         res: (_|_){
           // [incomplete] invalid interpolation: cannot convert incomplete value "string" to JSON:
-          //     ./workflows.cue:126:9
+          //     ./workflows.cue:129:9
         }
       }
     }
@@ -2036,7 +2137,7 @@
       _#type(:ci): (string){ string }
       if: (_|_){
         // [incomplete] repository_dispatch._#dispatchJob.if: invalid interpolation: non-concrete value string (type string):
-        //     ./workflows.cue:139:14
+        //     ./workflows.cue:142:14
       }
     }
     name: (string){ "Repository Dispatch" }
@@ -2244,7 +2345,7 @@
       }
     }
   }
-  rebuild_tip_cuelang_org: (#struct){
+  tip_triggers: (#struct){
     #architecture: (string){ |((string){ "ARM32" }, (string){ "x64" }, (string){ "x86" }) }
     #branch: (list){
       0: (string){ strings.MinRunes(1) }
@@ -2291,6 +2392,10 @@
             name: (string){ "Rebuild tip.cuelang.org" }
             run: (string){ "curl -f -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.CuelangOrgTipRebuildHook }}" }
           }
+          1: (#struct){
+            name: (string){ "Trigger unity build" }
+            run: (string){ "curl -s -H \"Content-Type: application/json\" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary \"{\"event_type\": \"Check against ${GITHUB_SHA}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"commit:${GITHUB_SHA})\\\"\"}}}\" https://api.github.com/repos/cue-sh/unity/dispatches" }
+          }
         }
         defaults: (#struct){
           run: (#struct){
@@ -2299,7 +2404,7 @@
         }
       }
     }
-    name: (string){ "Push to tip" }
+    name: (string){ "Push to tip triggers" }
     on: (#struct){
       push: (#struct){
         branches: (#list){
@@ -2308,6 +2413,74 @@
       }
     }
   }
+  new_version_triggers: (#struct){
+    #architecture: (string){ |((string){ "ARM32" }, (string){ "x64" }, (string){ "x86" }) }
+    #branch: (list){
+      0: (string){ strings.MinRunes(1) }
+    }
+    #configuration: ((bool|string|list|struct|number)){ |((string){ string }, (number){ number }, (bool){ bool }, (#struct){
+      }, (list){
+      }) }
+    #container: (#struct){
+      image: (string){ string }
+    }
+    #defaults: (#struct){
+    }
+    #env: (#struct){
+    }
+    #environment: (#struct){
+      name: (string){ string }
+    }
+    #event: (string){ |((string){ "check_run" }, (string){ "check_suite" }, (string){ "create" }, (string){ "delete" }, (string){ "deployment" }, (string){ "deployment_status" }, (string){ "fork" }, (string){ "gollum" }, (string){ "issue_comment" }, (string){ "issues" }, (string){ "label" }, (string){ "member" }, (string){ "milestone" }, (string){ "page_build" }, (string){ "project" }, (string){ "project_card" }, (string){ "project_column" }, (string){ "public" }, (string){ "pull_request" }, (string){ "pull_request_review" }, (string){ "pull_request_review_comment" }, (string){ "pull_request_target" }, (string){ "push" }, (string){ "registry_package" }, (string){ "release" }, (string){ "status" }, (string){ "watch" }, (string){ "workflow_dispatch" }, (string){ "workflow_run" }, (string){ "repository_dispatch" }) }
+    #eventObject: ((null|struct)){ |((null){ null }, (#struct){
+      }) }
+    #expressionSyntax: (string){ =~"^\\$\\{\\{.*\\}\\}$" }
+    #globs: (list){
+      0: (string){ strings.MinRunes(1) }
+    }
+    #machine: (string){ |((string){ "linux" }, (string){ "macos" }, (string){ "windows" }) }
+    #name: (string){ =~"^[_a-zA-Z][a-zA-Z0-9_-]*$" }
+    #path: (list){
+      0: (string){ strings.MinRunes(1) }
+    }
+    #ref: ((null|struct)){ |((null){ null }, (#struct){
+      }) }
+    #shell: (string){ |((string){ string }, (string){ "bash" }, (string){ "pwsh" }, (string){ "python" }, (string){ "sh" }, (string){ "cmd" }, (string){ "powershell" }) }
+    #types: (list){
+      0: (_){ _ }
+    }
+    #: (#struct){
+      "working-directory": (string){ string }
+    }
+    jobs: (#struct){
+      push: (#struct){
+        "runs-on": (string){ "ubuntu-18.04" }
+        steps: (#list){
+          0: (#struct){
+            name: (string){ "Rebuild tip.cuelang.org" }
+            run: (string){ "curl -s -H \"Content-Type: application/json\" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary \"{\"event_type\": \"Re-test post release of ${GITHUB_REF##refs/tags/}\"}\" https://api.github.com/repos/cuelang/cuelang.org/dispatches" }
+          }
+          1: (#struct){
+            name: (string){ "Trigger unity build" }
+            run: (string){ "curl -s -H \"Content-Type: application/json\" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary \"{\"event_type\": \"Check against CUE ${GITHUB_REF##refs/tags/}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"${GITHUB_REF##refs/tags/}}})\\\"\"}}}\" https://api.github.com/repos/cue-sh/unity/dispatches" }
+          }
+        }
+        defaults: (#struct){
+          run: (#struct){
+            shell: (string){ "bash" }
+          }
+        }
+      }
+    }
+    name: (string){ "New release triggers" }
+    on: (#struct){
+      push: (#struct){
+        tags: (#list){
+          0: (string){ "v*" }
+        }
+      }
+    }
+  }
   _#bashWorkflow(:ci): (#struct){
     #architecture: (string){ |((string){ "ARM32" }, (string){ "x64" }, (string){ "x86" }) }
     #branch: (list){
@@ -2400,7 +2573,7 @@
     name: (string){ "Set go build tags" }
     run: (_|_){
       // [incomplete] _#setGoBuildTags.run: invalid interpolation: non-concrete value string (type string):
-      //     ./workflows.cue:285:10
+      //     ./workflows.cue:311:10
     }
   }
   _#installGo(:ci): (#struct){
@@ -2454,7 +2627,7 @@
   _#cueckooCopybaraImage(:ci): (string){ "cueckoo/copybara:afc4ae03eed00b0c9d7415141cd1b5dfa583da7c" }
   _#copybaraCmd(:ci): (_|_){
     // [incomplete] _#copybaraCmd: invalid interpolation: non-concrete value string (type string):
-    //     ./workflows.cue:352:2
+    //     ./workflows.cue:378:2
     _#cmd(:ci): (string){ string }
   }
   _#copybaraSteps(:ci): (#list){
@@ -2468,7 +2641,7 @@
       name: (string){ string }
       run: (_|_){
         // [incomplete] _#copybaraSteps.1.run: invalid interpolation: non-concrete value string (type string):
-        //     ./workflows.cue:352:2
+        //     ./workflows.cue:378:2
         _#cmd(:ci): (string){ string }
       }
     }
@@ -2499,8 +2672,12 @@
       schema: 〈1;release〉
     },
     {
-      file: "rebuild_tip_cuelang_org.yml"
-      schema: 〈1;rebuild_tip_cuelang_org〉
+      file: "tip_triggers.yml"
+      schema: 〈1;tip_triggers〉
+    },
+    {
+      file: "new_version_triggers.yml"
+      schema: 〈1;new_version_triggers〉
     },
     {
       file: "mirror.yml"
@@ -2762,8 +2939,8 @@
       }
     }
   })
-  rebuild_tip_cuelang_org: (〈0;_#bashWorkflow〉 & {
-    name: "Push to tip"
+  tip_triggers: (〈0;_#bashWorkflow〉 & {
+    name: "Push to tip triggers"
     on: {
       push: {
         branches: [
@@ -2779,6 +2956,35 @@
             name: "Rebuild tip.cuelang.org"
             run: "curl -f -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.CuelangOrgTipRebuildHook }}"
           },
+          {
+            name: "Trigger unity build"
+            run: "curl -s -H \"Content-Type: application/json\" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary \"{\"event_type\": \"Check against ${GITHUB_SHA}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"commit:${GITHUB_SHA})\\\"\"}}}\" https://api.github.com/repos/cue-sh/unity/dispatches"
+          },
+        ]
+      }
+    }
+  })
+  new_version_triggers: (〈0;_#bashWorkflow〉 & {
+    name: "New release triggers"
+    on: {
+      push: {
+        tags: [
+          〈3;_#releaseTagPattern〉,
+        ]
+      }
+    }
+    jobs: {
+      push: {
+        "runs-on": 〈3;_#linuxMachine〉
+        steps: [
+          {
+            name: "Rebuild tip.cuelang.org"
+            run: "curl -s -H \"Content-Type: application/json\" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary \"{\"event_type\": \"Re-test post release of ${GITHUB_REF##refs/tags/}\"}\" https://api.github.com/repos/cuelang/cuelang.org/dispatches"
+          },
+          {
+            name: "Trigger unity build"
+            run: "curl -s -H \"Content-Type: application/json\" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary \"{\"event_type\": \"Check against CUE ${GITHUB_REF##refs/tags/}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"${GITHUB_REF##refs/tags/}}})\\\"\"}}}\" https://api.github.com/repos/cue-sh/unity/dispatches"
+          },
         ]
       }
     }
diff --git a/internal/ci/workflows.cue b/internal/ci/workflows.cue
index e1ad34d..5e6a9aa 100644
--- a/internal/ci/workflows.cue
+++ b/internal/ci/workflows.cue
@@ -39,8 +39,12 @@
 		schema: release
 	},
 	{
-		file:   "rebuild_tip_cuelang_org.yml"
-		schema: rebuild_tip_cuelang_org
+		file:   "tip_triggers.yml"
+		schema: tip_triggers
+	},
+	{
+		file:   "new_version_triggers.yml"
+		schema: new_version_triggers
 	},
 	{
 		file:   "mirror.yml"
@@ -315,16 +319,47 @@
 	}
 }
 
-rebuild_tip_cuelang_org: _#bashWorkflow & {
+tip_triggers: _#bashWorkflow & {
 
-	name: "Push to tip"
+	name: "Push to tip triggers"
 	on: push: branches: [_#masterBranch]
 	jobs: push: {
 		"runs-on": _#linuxMachine
-		steps: [{
-			name: "Rebuild tip.cuelang.org"
-			run:  "curl -f -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.CuelangOrgTipRebuildHook }}"
-		}]
+		steps: [
+			{
+				name: "Rebuild tip.cuelang.org"
+				run:  "curl -f -X POST -d {} https://api.netlify.com/build_hooks/${{ secrets.CuelangOrgTipRebuildHook }}"
+			},
+			{
+				name: "Trigger unity build"
+				run: """
+					curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary "{\"event_type\": \"Check against ${GITHUB_SHA}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"commit:${GITHUB_SHA})\\\"\"}}}" https://api.github.com/repos/cue-sh/unity/dispatches
+					"""
+			},
+		]
+	}
+}
+
+new_version_triggers: _#bashWorkflow & {
+
+	name: "New release triggers"
+	on: push: tags: [_#releaseTagPattern]
+	jobs: push: {
+		"runs-on": _#linuxMachine
+		steps: [
+			{
+				name: "Rebuild tip.cuelang.org"
+				run: """
+					curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary "{\"event_type\": \"Re-test post release of ${GITHUB_REF##refs/tags/}\"}" https://api.github.com/repos/cuelang/cuelang.org/dispatches
+					"""
+			},
+			{
+				name: "Trigger unity build"
+				run: """
+					curl -s -H "Content-Type: application/json" -u cueckoo:$CUECKOO_GITHUB_PAT --request POST --data-binary "{\"event_type\": \"Check against CUE ${GITHUB_REF##refs/tags/}\", \"client_payload\": {\"type\": \"unity\", \"payload\": {\"versions\": \"\\\"${GITHUB_REF##refs/tags/}}})\\\"\"}}}" https://api.github.com/repos/cue-sh/unity/dispatches
+					"""
+			},
+		]
 	}
 }