Skip to content

Commit 03dae33

Browse files
committed
Automate Boatstack sync and verified releases
1 parent 29184bc commit 03dae33

3 files changed

Lines changed: 156 additions & 6 deletions

File tree

.github/workflows/auto-release.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Boatstack-owned control plane.
2+
name: Publish verified Boatstack release
3+
4+
on:
5+
workflow_run:
6+
workflows: ["Verify Boatstack distribution"]
7+
types: [completed]
8+
9+
permissions:
10+
contents: read
11+
12+
concurrency:
13+
group: auto-release-boatstack
14+
cancel-in-progress: false
15+
16+
jobs:
17+
release:
18+
if: >-
19+
github.event.workflow_run.conclusion == 'success' &&
20+
github.event.workflow_run.event == 'push' &&
21+
github.event.workflow_run.head_branch == 'main'
22+
runs-on: ubuntu-latest
23+
steps:
24+
- name: Create repository automation token
25+
id: app-token
26+
uses: actions/create-github-app-token@v3
27+
with:
28+
client-id: ${{ vars.BOATSTACK_APP_CLIENT_ID }}
29+
private-key: ${{ secrets.BOATSTACK_APP_PRIVATE_KEY }}
30+
owner: operatorstack
31+
repositories: boatstack
32+
permission-contents: write
33+
- uses: actions/checkout@v4
34+
with:
35+
ref: main
36+
fetch-depth: 0
37+
token: ${{ steps.app-token.outputs.token }}
38+
- uses: actions/setup-go@v5
39+
with:
40+
go-version-file: boatstack/go.mod
41+
cache-dependency-path: boatstack/go.mod
42+
- name: Detect release-bearing projection
43+
id: classify
44+
shell: bash
45+
run: |
46+
if [[ ! -f boatstack/release.go ]]; then
47+
echo "The release classifier is not projected yet; no tag will be created."
48+
echo "release_required=false" >> "$GITHUB_OUTPUT"
49+
exit 0
50+
fi
51+
latest_tag="$(git describe --tags --abbrev=0 --match 'v[0-9]*' 2>/dev/null || true)"
52+
if [[ -z "$latest_tag" ]]; then
53+
echo "BLOCKED: automatic patch releases require an existing stable tag." >&2
54+
exit 1
55+
fi
56+
classification="$(cd boatstack && go run ./cmd/boatstack-helper \
57+
release-classify --repo .. --base "$latest_tag" --head HEAD)"
58+
printf '%s\n' "$classification" >> "$GITHUB_OUTPUT"
59+
echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT"
60+
- name: Create next verified patch tag
61+
if: steps.classify.outputs.release_required == 'true'
62+
env:
63+
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
64+
LATEST_TAG: ${{ steps.classify.outputs.latest_tag }}
65+
shell: bash
66+
run: |
67+
next_tag="$(cd boatstack && go run ./cmd/boatstack-helper \
68+
next-patch --version "$LATEST_TAG")"
69+
if git rev-parse --verify --quiet "refs/tags/$next_tag"; then
70+
echo "BLOCKED: tag already exists: $next_tag" >&2
71+
exit 1
72+
fi
73+
git config user.name "${APP_SLUG}[bot]"
74+
git config user.email "${APP_SLUG}[bot]@users.noreply.github.com"
75+
git tag -a "$next_tag" -m "Boatstack $next_tag"
76+
git push origin "$next_tag"
77+
echo "Published verified release tag $next_tag."
78+
- name: Report documentation-only sync
79+
if: steps.classify.outputs.release_required != 'true'
80+
run: echo "Boatstack content is current; this merge does not require new binaries."

.github/workflows/ci.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,43 @@ jobs:
6767
$errors | ForEach-Object { Write-Error $_ }
6868
exit 1
6969
}
70+
71+
auto-merge-sync:
72+
if: >-
73+
github.event_name == 'pull_request' &&
74+
github.event.pull_request.head.repo.full_name == github.repository &&
75+
startsWith(github.head_ref, 'sync/intelligence-flow-')
76+
needs: test
77+
runs-on: ubuntu-latest
78+
steps:
79+
- name: Create repository automation token
80+
id: app-token
81+
uses: actions/create-github-app-token@v3
82+
with:
83+
client-id: ${{ vars.BOATSTACK_APP_CLIENT_ID }}
84+
private-key: ${{ secrets.BOATSTACK_APP_PRIVATE_KEY }}
85+
owner: operatorstack
86+
repositories: boatstack
87+
permission-contents: write
88+
permission-pull-requests: write
89+
- uses: actions/checkout@v4
90+
with:
91+
ref: ${{ github.event.pull_request.head.sha }}
92+
- name: Verify generated projection provenance
93+
env:
94+
APP_SLUG: ${{ steps.app-token.outputs.app-slug }}
95+
HEAD_BRANCH: ${{ github.head_ref }}
96+
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
97+
shell: bash
98+
run: |
99+
source_repo="$(jq -r '.source.repo' UPSTREAM.json)"
100+
source_commit="$(jq -r '.source.commit' UPSTREAM.json)"
101+
short="${source_commit:0:12}"
102+
[[ "$PR_AUTHOR" == "${APP_SLUG}[bot]" ]]
103+
[[ "$source_repo" == "operatorstack/intelligence-flow" ]]
104+
[[ "$HEAD_BRANCH" == "sync/intelligence-flow-$short" ]]
105+
- name: Merge verified generated PR
106+
env:
107+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
108+
PR_URL: ${{ github.event.pull_request.html_url }}
109+
run: gh pr merge "$PR_URL" --squash

.github/workflows/sync-upstream.yml

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ on:
55
schedule:
66
- cron: "17 */6 * * *"
77
workflow_dispatch:
8+
inputs:
9+
source_commit:
10+
description: Exact Intelligence Flow commit to project (defaults to main)
11+
required: false
12+
type: string
813

914
permissions:
1015
contents: write
@@ -18,29 +23,53 @@ jobs:
1823
sync:
1924
runs-on: ubuntu-latest
2025
steps:
26+
- name: Create repository automation token
27+
id: app-token
28+
uses: actions/create-github-app-token@v3
29+
with:
30+
client-id: ${{ vars.BOATSTACK_APP_CLIENT_ID }}
31+
private-key: ${{ secrets.BOATSTACK_APP_PRIVATE_KEY }}
32+
owner: operatorstack
33+
repositories: |
34+
intelligence-flow
35+
boatstack
36+
permission-contents: write
37+
permission-pull-requests: write
2138
- name: Check out Boatstack
2239
uses: actions/checkout@v4
2340
with:
2441
path: boatstack-repo
42+
token: ${{ steps.app-token.outputs.token }}
2543
- name: Check out Intelligence Flow
2644
uses: actions/checkout@v4
2745
with:
2846
repository: operatorstack/intelligence-flow
29-
ref: main
47+
ref: ${{ inputs.source_commit || 'main' }}
48+
fetch-depth: 0
3049
path: intelligence-flow
50+
token: ${{ steps.app-token.outputs.token }}
3151
- name: Generate projection
3252
id: generate
3353
shell: bash
3454
run: |
3555
source_commit="$(git -C intelligence-flow log -1 --format=%H -- examples/12-product-engineering-loop)"
56+
current_commit="$(jq -r '.source.commit // empty' boatstack-repo/UPSTREAM.json)"
57+
if [[ -n "$current_commit" ]] &&
58+
! git -C intelligence-flow merge-base --is-ancestor "$current_commit" "$source_commit"; then
59+
echo "Ignoring stale projection request $source_commit; Boatstack already records $current_commit."
60+
echo "stale=true" >> "$GITHUB_OUTPUT"
61+
exit 0
62+
fi
3663
python3 intelligence-flow/examples/12-product-engineering-loop/scripts/build_boatstack.py \
3764
--repo boatstack-repo \
3865
--source-commit "$source_commit" \
3966
--write
4067
echo "source_commit=$source_commit" >> "$GITHUB_OUTPUT"
68+
echo "stale=false" >> "$GITHUB_OUTPUT"
4169
- name: Open generated pull request
70+
if: steps.generate.outputs.stale != 'true'
4271
env:
43-
GH_TOKEN: ${{ github.token }}
72+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
4473
SOURCE_COMMIT: ${{ steps.generate.outputs.source_commit }}
4574
shell: bash
4675
run: |
@@ -92,13 +121,14 @@ jobs:
92121
echo "Upstream PR already open: $existing"
93122
exit 0
94123
fi
95-
git config user.name "github-actions[bot]"
96-
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
124+
git config user.name "${{ steps.app-token.outputs.app-slug }}[bot]"
125+
git config user.email "${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com"
97126
git switch -c "$branch"
98127
git commit -m "Sync Boatstack from Intelligence Flow $short"
99128
git push --set-upstream origin "$branch"
100-
gh pr create \
129+
pr_url="$(gh pr create \
101130
--base main \
102131
--head "$branch" \
103132
--title "Sync Boatstack from Intelligence Flow $short" \
104-
--body-file "$body_file"
133+
--body-file "$body_file")"
134+
echo "Opened generated PR: $pr_url"

0 commit comments

Comments
 (0)