Skip to content

Commit a3f3e1e

Browse files
y3rshcursoragent
andcommitted
Document Flex tagging, tag push order, and track-builds behavior.
Align the workspace rule, README, and release guide generator with current go.py and track_builds.py behavior. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 3fdb2f8 commit a3f3e1e

3 files changed

Lines changed: 166 additions & 9 deletions

File tree

.cursor/rules/robot-stack.mdc

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The system supports internal and external release channels. This tool helps enfo
2121

2222
| Path | Repos synced | App repo (tagging) | Version scheme |
2323
|---|---|---|---|
24-
| **Flex** | `opentrons`, `oe-core`, `ot3-firmware` | `opentrons` | Semver with `v` prefix (e.g. `v8.5.0`, `v8.5.0-alpha.6`) |
24+
| **Flex** | `opentrons`, `oe-core`, `ot3-firmware` | `opentrons` | Per-repo prefixes (see Flex semver below) |
2525
| **OT-2** | `opentrons-ot2`, `buildroot` | `opentrons-ot2` | Calendar semver (see below) |
2626

2727
`robot-stack-infra` is always cloned and pulled for both paths as a reference repo, but is not included in release tables or tagging.
@@ -68,6 +68,58 @@ External alpha/beta use semver prerelease numbering on a fixed base: `v26.6.0-al
6868

6969
External tags use a `v` prefix; internal tags use `internal@`.
7070

71+
### Flex semver tagging
72+
73+
Flex uses **different tag prefixes per repo**. In `just go`, Flex prompts for **Stability: stable/unstable** (not OT-2's alpha/beta/stable). `unstable` means alpha builds.
74+
75+
| Repo | Internal | External |
76+
|---|---|---|
77+
| `opentrons` (app) | `ot3@X.Y.Z`, alpha `ot3@X.Y.Z-alpha.N` | `vX.Y.Z`, alpha `vX.Y.Z-alpha.N` |
78+
| `oe-core` (robot OS) | `internal@X.Y.Z`, alpha `internal@X.Y.Z-alpha.N` | `v0.X.Y` (independent line; patch bump from latest merged tag) |
79+
| `ot3-firmware` | `internal@vN` integer counter | `vN` integer counter |
80+
81+
Tag suggestion rules in `automation/go.py`:
82+
83+
- **App (`opentrons`):** base `X.Y.Z` comes from the prompted release version. Stable: `ot3@X.Y.Z` if missing, else patch bump. Alpha: increment `ot3@X.Y.Z-alpha.N` from branch tags.
84+
- **Robot OS (`oe-core`, internal):** base `X.Y.Z` comes from the same prompted version (not from the newest oe-core tag alone). Alpha `N` is **coordinated with the app**: `go` reads the next `ot3@X.Y.Z-alpha.N` from `opentrons` and reuses that `N` for `internal@X.Y.Z-alpha.N`. Stable: `internal@X.Y.Z` if missing, else patch bump.
85+
- **Firmware (`ot3-firmware`):** next integer above the highest merged channel tag (`internal@vN` or `vN`).
86+
87+
### Tag push order
88+
89+
Push annotated tags in this order. Dependent stack repos first, app monorepo last.
90+
91+
| Path | Order |
92+
|---|---|
93+
| **Flex** | 1. `ot3-firmware` (if needed), 2. `oe-core` (if needed), 3. `opentrons` (app, always last) |
94+
| **OT-2** | 1. `buildroot` (if needed), 2. `opentrons-ot2` (app, always last) |
95+
96+
`go` prints this order at the end of a release run.
97+
98+
### Track release builds (`just track-builds`)
99+
100+
After pushing the **app** tag, run `just track-builds --path flex|ot2 --tag <app-tag> --wait` (or without `--wait` for a one-shot lookup). `go` prints this command with `--wait` by default.
101+
102+
`automation/track_builds.py` locates GitHub Actions runs for:
103+
104+
1. **App** workflow on the monorepo (`App test, build, and deploy`)
105+
2. **Kickoff** cross-repo dispatch (`Start OT-2 build` or `Start Flex build`)
106+
3. **Robot OS** build in `buildroot` (OT-2) or `oe-core` (Flex)
107+
108+
The Rich table also lists matching **key jobs** (deploy, desktop builds, dispatch spawn, robot image build). The **Slack copy block** includes only two links:
109+
110+
```
111+
OT-2 release `internal@26.5.2801`
112+
113+
- app: https://github.com/Opentrons/opentrons-ot2/actions/runs/...
114+
- ot2: https://github.com/Opentrons/buildroot/actions/runs/...
115+
```
116+
117+
(Flex uses `- flex:` instead of `- ot2:`.)
118+
119+
**`--wait` behavior:** poll every 15 seconds (default) until App, Kickoff, and Robot OS workflow runs all appear, up to 900 seconds (default). The poll loop only checks for workflow runs (not jobs). After all three runs exist, the script fetches key jobs with retries for transient GitHub 404s on the jobs API. Exit code `2` if any top-level run is still missing.
120+
121+
Tag prefix inference when `--path` is omitted: `internal@` → OT-2, `ot3@` → Flex, otherwise Flex.
122+
71123
## Development Standards
72124

73125
### Language & Version

README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,53 @@ Instead of make, use [just](https://github.com/casey/just). The VS Code justfile
1616
- `uv run just fix`
1717
- sync repos and inspect release state
1818
- `uv run just go`
19+
- find GitHub Actions runs after pushing an app tag
20+
- `uv run just track-builds --path ot2 --tag internal@26.5.2801 --wait`
1921

2022
## Release Paths
2123

2224
`just go` runs `automation/go.py`, an interactive release helper. It supports two robot paths; **Flex is the default**.
2325

2426
| Path | Repos | App repo | Version scheme |
2527
|---|---|---|---|
26-
| **Flex** | `opentrons`, `oe-core`, `ot3-firmware` | `opentrons` | Semver (`v8.5.0`, alpha tags like `v8.5.0-alpha.6`) |
28+
| **Flex** | `opentrons`, `oe-core`, `ot3-firmware` | `opentrons` | Per-repo prefixes (`ot3@`, `internal@`, `v`; see below) |
2729
| **OT-2** | `opentrons-ot2`, `buildroot` | `opentrons-ot2` | Calendar semver (internal + external; see below) |
2830

2931
`robot-stack-infra` is always cloned and pulled for both paths as a reference repo. It is not included in release tables or tagging.
3032

3133
Each repo uses isolation branches named `chore_release-<version>` during a release cycle.
3234

35+
### Tag push order
36+
37+
Push annotated tags in this order. Stack repos first, app monorepo last.
38+
39+
| Path | Order |
40+
|---|---|
41+
| **Flex** | `ot3-firmware` (if needed) → `oe-core` (if needed) → `opentrons` (app, always last) |
42+
| **OT-2** | `buildroot` (if needed) → `opentrons-ot2` (app, always last) |
43+
44+
### Flex semver
45+
46+
Flex repos use different tag prefixes. In `just go`, Flex uses **stable/unstable** stability (unstable = alpha).
47+
48+
| Repo | Internal | External |
49+
|---|---|---|
50+
| `opentrons` | `ot3@X.Y.Z`, alpha `ot3@X.Y.Z-alpha.N` | `vX.Y.Z`, alpha `vX.Y.Z-alpha.N` |
51+
| `oe-core` | `internal@X.Y.Z`, alpha `internal@X.Y.Z-alpha.N` | `v0.X.Y` (independent line) |
52+
| `ot3-firmware` | `internal@vN` | `vN` |
53+
54+
For internal alpha builds, `go` coordinates oe-core alpha numbers with the next `ot3@X.Y.Z-alpha.N` on `opentrons`. Internal oe-core and app stable tags use the prompted base version; if that exact tag already exists on the branch, `go` suggests a patch bump.
55+
56+
### Track release builds
57+
58+
After pushing the app tag, locate CI with `just track-builds`:
59+
60+
```bash
61+
just track-builds --path ot2 --tag internal@26.5.2801 --wait
62+
```
63+
64+
The script finds app, kickoff, and robot OS workflow runs and prints a Rich table plus a Slack copy block with two links (`app` and `ot2` or `flex`). With `--wait`, it polls every 15 seconds until all three workflow runs appear (default timeout 15 minutes).
65+
3366
## OT-2 calendar semver
3467

3568
OT-2 uses semver-shaped versions so electron-updater and robot update checks work. **Internal and external channels use different patch schemes.** Within a channel, the app and robot OS share the same version string.

automation/release_guides.py

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,55 @@ def _tag_need_section() -> str:
199199
"""
200200

201201

202+
def _tag_push_order_section(robot: str) -> str:
203+
"""Explain annotated tag push order for one robot path."""
204+
if robot == "flex":
205+
rows = """
206+
<tr><td>1</td><td><code>ot3-firmware</code></td><td>Firmware, if a new tag is needed</td></tr>
207+
<tr><td>2</td><td><code>oe-core</code></td><td>Robot OS, if a new tag is needed</td></tr>
208+
<tr><td>3</td><td><code>opentrons</code></td><td>App monorepo, always last</td></tr>"""
209+
path_label = "Flex"
210+
else:
211+
rows = """
212+
<tr><td>1</td><td><code>buildroot</code></td><td>Robot OS, if a new tag is needed</td></tr>
213+
<tr><td>2</td><td><code>opentrons-ot2</code></td><td>App monorepo, always last</td></tr>"""
214+
path_label = "OT-2"
215+
return f"""
216+
<h2>Tag push order</h2>
217+
<p>Push annotated tags in this order. Dependent stack repos first, app monorepo last.
218+
<code>just go</code> prints this reminder at the end of a release run.</p>
219+
<table>
220+
<thead><tr><th>Step</th><th>Repo</th><th>Notes</th></tr></thead>
221+
<tbody>{rows}</tbody>
222+
</table>
223+
<p>{path_label} stack repos only get a new tag when their release branch tip is ahead of the
224+
latest channel tag on that branch.</p>
225+
"""
226+
227+
228+
def _track_builds_section(robot: str, example_tag: str, slack_robot_label: str) -> str:
229+
"""Explain just track-builds for one robot path."""
230+
path_flag = html.escape(robot)
231+
tag = html.escape(example_tag)
232+
slack_label = html.escape(slack_robot_label)
233+
return f"""
234+
<h2>Track release builds</h2>
235+
<p>After pushing the app tag, run:</p>
236+
<pre>just track-builds --path {path_flag} --tag {tag} --wait</pre>
237+
<p><code>automation/track_builds.py</code> locates GitHub Actions runs for the app workflow,
238+
the cross-repo kickoff workflow, and the robot OS build in
239+
<code>{'buildroot' if robot == 'ot2' else 'oe-core'}</code>.</p>
240+
<p>The Rich output includes a full table (including key jobs). The Slack copy block is shorter:</p>
241+
<pre>{'OT-2' if robot == 'ot2' else 'Flex'} release `{tag}`
242+
243+
- app: &lt;app workflow run URL&gt;
244+
- {slack_label}: &lt;robot OS workflow run URL&gt;</pre>
245+
<p><strong><code>--wait</code></strong> polls every 15 seconds until app, kickoff, and robot OS
246+
workflow runs all appear (default timeout 15 minutes). It avoids calling the jobs API during
247+
polling, then retries job lookups if GitHub briefly returns 404 for a new run.</p>
248+
"""
249+
250+
202251
def _yaml_links(channel_host: str) -> str:
203252
"""Render electron-updater YAML links for a host."""
204253
items = []
@@ -256,6 +305,10 @@ def render_flex_external() -> str:
256305
<p>When a new tag is needed, <code>go</code> takes the highest <code>vN</code> number merged
257306
into the branch and suggests <code>v(N+1)</code>.</p>
258307
308+
{_tag_push_order_section("flex")}
309+
310+
{_track_builds_section("flex", "v9.1.0", "flex")}
311+
259312
<h2>Where to find published releases</h2>
260313
<p>External Flex artifacts live on <code>{html.escape(FLEX_EXTERNAL.app_host)}</code>.</p>
261314
<table>
@@ -320,22 +373,33 @@ def render_flex_internal() -> str:
320373
<h2>How the next tag is chosen</h2>
321374
<h3>App (<code>opentrons</code>)</h3>
322375
<ul>
323-
<li><strong>Stable:</strong> <code>ot3@X.Y.Z</code> if not already on the branch.</li>
324-
<li><strong>Alpha (unstable):</strong> increment <code>ot3@X.Y.Z-alpha.N</code> from existing tags on the branch.</li>
376+
<li><strong>Stable:</strong> <code>ot3@X.Y.Z</code> if not already on the branch; otherwise patch bump
377+
(e.g. <code>ot3@8.5.0</code> → <code>ot3@8.5.1</code>).</li>
378+
<li><strong>Alpha (unstable):</strong> increment <code>ot3@X.Y.Z-alpha.N</code> from existing tags on the branch
379+
(first alpha is <code>ot3@8.5.0-alpha.0</code>).</li>
325380
</ul>
326381
327382
<h3>Robot OS (<code>oe-core</code>)</h3>
328383
<p>Internal tags use the <code>internal@</code> prefix without a leading <code>v</code>.
329-
The base version (e.g. <code>3.0.0</code>) comes from the newest <code>internal@</code> tag on the branch.</p>
384+
The base <code>X.Y.Z</code> comes from the same version you enter at the <code>just go</code> prompt
385+
(not from the newest oe-core tag alone).</p>
330386
<ul>
331-
<li><strong>Alpha (unstable):</strong> <code>internal@3.0.0-alpha.N</code>, increment <code>N</code>.</li>
332-
<li><strong>Stable:</strong> <code>internal@3.0.0</code>, or patch bump if that exact tag already exists.</li>
387+
<li><strong>Alpha (unstable):</strong> <code>internal@X.Y.Z-alpha.N</code>. The alpha number
388+
<code>N</code> is coordinated with the app: <code>go</code> reads the next
389+
<code>ot3@X.Y.Z-alpha.N</code> from <code>opentrons</code> and reuses that <code>N</code>
390+
for oe-core.</li>
391+
<li><strong>Stable:</strong> <code>internal@X.Y.Z</code> if that exact tag is not on the branch;
392+
otherwise patch bump (e.g. <code>internal@8.5.0</code> → <code>internal@8.5.1</code>).</li>
333393
</ul>
334394
335395
<h3>Firmware (<code>ot3-firmware</code>)</h3>
336396
<p>Internal tags look like <code>internal@v26</code>, <code>internal@v27</code>.
337397
When needed, <code>go</code> suggests one higher than the max merged tag number.</p>
338398
399+
{_tag_push_order_section("flex")}
400+
401+
{_track_builds_section("flex", "ot3@8.5.0-alpha.0", "flex")}
402+
339403
<h2>Where to find published releases</h2>
340404
<p>Internal Flex artifacts live on <code>{html.escape(FLEX_INTERNAL.app_host)}</code>.</p>
341405
<table>
@@ -358,8 +422,8 @@ def render_flex_internal() -> str:
358422
<table>
359423
<thead><tr><th>Stability</th><th>App tag example</th><th>oe-core example</th></tr></thead>
360424
<tbody>
361-
<tr><td>Stable internal</td><td><code>ot3@8.5.0</code> (uncommon)</td><td><code>internal@3.0.0</code></td></tr>
362-
<tr><td>Alpha</td><td><code>ot3@8.5.0-alpha.0</code></td><td><code>internal@3.0.0-alpha.0</code></td></tr>
425+
<tr><td>Stable internal</td><td><code>ot3@8.5.0</code></td><td><code>internal@8.5.0</code> (same prompted base)</td></tr>
426+
<tr><td>Alpha</td><td><code>ot3@8.5.0-alpha.0</code></td><td><code>internal@8.5.0-alpha.0</code> (same <code>N</code> as app)</td></tr>
363427
<tr><td>Beta</td><td><code>ot3@8.5.0-beta.0</code> (manual)</td><td><code>internal@3.0.0-beta.0</code> (manual)</td></tr>
364428
</tbody>
365429
</table>
@@ -419,6 +483,10 @@ def render_ot2_external() -> str:
419483
<p><code>buildroot</code> uses the same next-tag logic and should receive the matching tag when its
420484
branch has commits since the latest channel tag.</p>
421485
486+
{_tag_push_order_section("ot2")}
487+
488+
{_track_builds_section("ot2", "v26.6.0", "ot2")}
489+
422490
<h2>Where to find published releases</h2>
423491
<p>External OT-2 artifacts live on <code>{html.escape(OT2_EXTERNAL.app_host)}</code>.</p>
424492
<table>
@@ -508,6 +576,10 @@ def render_ot2_internal() -> str:
508576
<p>Examples: <code>internal@26.5.2601-alpha</code>, <code>internal@26.5.2602-alpha</code>.</p>
509577
<p><code>buildroot</code> receives the matching tag when its branch is ahead of the latest internal tag.</p>
510578
579+
{_tag_push_order_section("ot2")}
580+
581+
{_track_builds_section("ot2", "internal@26.5.2601", "ot2")}
582+
511583
<h2>Where to find published releases</h2>
512584
<p>Internal OT-2 artifacts live on <code>{html.escape(OT2_INTERNAL.app_host)}</code>.</p>
513585
<table>

0 commit comments

Comments
 (0)