Repository navigation
154 lines (145 loc) · 7.22 KB
/
Copy pathwrite-back.yml
File metadata and controls
154 lines (145 loc) · 7.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
name: Reporter Write-Back
# Tell a reporter, at most once per channel, that the fix they reported is out.
#
# "A reporter", not "a bug reporter": the candidate enumeration no longer
# narrows on the Bug label, because whether someone hears back should not depend
# on how triage classified what they raised.
#
# BOTH channels run, and they say different things. A `-beta.N` cut tells the
# reporter their fix is available now in a beta and that a second message is
# coming; the stable promotion sends that second message. The two are kept
# apart by their notification markers, which key on the version, so neither can
# fire twice and a cycle that cuts beta.0 through beta.7 still produces exactly
# one beta reply.
#
# This used to be stable-only, on the reasoning that a reporter told "your fix
# is out" should be able to install it from the default channel. That reasoning
# holds, and it is why the beta reply does not make that claim: it names the
# beta as a beta, points at a build that must be downloaded deliberately, and
# promises the follow-up rather than implying none is coming.
#
# WHY IT IS DECOUPLED
# This posts in public under the project's name and it runs after a release
# has already happened. Nothing it does may reach back into the release: it
# has its own trigger and its own concurrency group, so it can never queue
# behind or delay a cut, and no release workflow waits on its result. The
# worst outcome available to a failure here is that nobody gets told.
#
# WHY THERE ARE TWO TOKENS
# The reply is posted onto an issue in this repo; the fix pull request and its
# changeset are read out of the private monorepo. No single credential can do
# both — this repo's own token cannot see agents-private at all, and the App
# installation token that can is scoped to that one repository. So both are
# passed and the script picks per call site by target repo.
#
# EVERYTHING SHIPS INERT
# With no LINEAR_API_KEY the script emits a notice and enumerates nothing.
# With the key but no WRITE_BACK_MODE it composes replies and logs them
# without performing a single write. Live posting therefore needs two
# deliberate operator actions, so wiring up credentials cannot arm
# reporter-facing posts by accident. Both steps are in RELEASES.md.
# Email has its own pair on top of those: with no RESEND_API_KEY contact
# emails are read but never used as a reply channel; with the key they are
# previewed; they are sent only when WRITE_BACK_EMAIL_MODE is also live
# alongside WRITE_BACK_MODE.
on:
schedule:
- cron: "43 * * * *"
repository_dispatch:
types: [desktop-release-published]
# Manual backfill / dry-run rehearsal against a specific tag.
workflow_dispatch:
inputs:
release_tag:
description: "Release tag to notify reporters about (e.g. v0.36.0 or v0.36.0-beta.3)"
required: true
type: string
dry_run:
description: "Preview replies without writing notification markers or posting"
required: false
type: boolean
default: true
permissions:
contents: read
# Posting the reply onto a GitHub issue origin in this repo, and closing that
# issue with the stable reply once every Linear ticket carrying it is resolved.
issues: write
jobs:
notify:
strategy:
fail-fast: false
matrix:
channel: ${{ fromJSON(github.event_name == 'schedule' && '["stable","beta"]' || contains(github.event.client_payload.release_tag || inputs.release_tag, '-beta.') && '["beta"]' || '["stable"]') }}
concurrency:
group: reporter-write-back-${{ matrix.channel }}
cancel-in-progress: false
runs-on: ubuntu-26.04
cache-mode: none
timeout-minutes: 10
env:
WRITE_BACK_CHANNEL: ${{ matrix.channel }}
RELEASE_TAG: ${{ github.event.client_payload.release_tag || inputs.release_tag }}
steps:
- name: Checkout with full history and tags
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
fetch-tags: true
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .node-version
- name: Resolve published release for reconciliation
id: tag
env:
GH_TOKEN: ${{ github.token }}
run: node .github/scripts/write-back-target.mjs
- name: Check whether the bridge App is configured
# Fix references point into the private monorepo, which this repo's own
# token cannot read at all. Secrets are unreadable in `if:` expressions,
# so the presence check runs here and gates the mint via an output.
if: steps.tag.outputs.channel != 'none'
id: bridge-check
env:
OK_RELEASE_BRIDGE_APP_ID: ${{ secrets.OK_RELEASE_BRIDGE_APP_ID }}
run: |
set -euo pipefail
if [[ -n "${OK_RELEASE_BRIDGE_APP_ID:-}" ]]; then
echo "configured=true" >> "$GITHUB_OUTPUT"
else
echo "configured=false" >> "$GITHUB_OUTPUT"
echo "::warning::OK_RELEASE_BRIDGE_APP_ID not set — reads against the private monorepo will 404 and those candidates will be reported as failures."
fi
- name: Mint cross-repo read token (bridge App)
if: steps.tag.outputs.channel != 'none' && steps.bridge-check.outputs.configured == 'true'
id: bridge-token
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
# Public client ID for inkeep-ok-release-bridge (non-secret; supersedes the deprecated app-id input).
client-id: Iv23lii4qS2FAc6tkzfI
private-key: ${{ secrets.OK_RELEASE_BRIDGE_APP_PRIVATE_KEY }}
# The bridge App is installed on agents-private, not here. Scoping the
# token to that installation is also why it cannot be the only token:
# it has no standing to post the reply back onto an issue in this repo.
owner: inkeep
repositories: agents-private
permission-contents: read
permission-pull-requests: read
- name: Notify reporters whose fix reached this release
if: steps.tag.outputs.channel != 'none'
env:
RELEASE_TAG: ${{ steps.tag.outputs.release_tag }}
# Posting the reply, which happens against this repo.
GH_TOKEN: ${{ github.token }}
# Reading the fix pull request and its changeset, which happen against
# agents-private. The script picks per call site by target repo.
CROSS_REPO_TOKEN: ${{ steps.bridge-token.outputs.token }}
LINEAR_API_KEY: ${{ secrets.LINEAR_API_KEY }}
# Absent by default. Set to `live` on the repo (a variable, not a
# secret, so it is visible in the settings UI) to arm real posts.
WRITE_BACK_MODE: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && 'dry-run' || vars.WRITE_BACK_MODE }}
DISCORD_NOTIFY_URL: ${{ secrets.DISCORD_NOTIFY_URL }}
DISCORD_NOTIFY_TOKEN: ${{ secrets.DISCORD_NOTIFY_TOKEN }}
RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
WRITE_BACK_EMAIL_MODE: ${{ vars.WRITE_BACK_EMAIL_MODE }}
run: node .github/scripts/write-back.mjs