Skip to content

ci: split release into firmware and PCB tag-triggered workflows#8

Merged
gaetanars merged 3 commits intomainfrom
refactor/split-release-tags
Apr 23, 2026
Merged

ci: split release into firmware and PCB tag-triggered workflows#8
gaetanars merged 3 commits intomainfrom
refactor/split-release-tags

Conversation

@gaetanars
Copy link
Copy Markdown
Owner

Summary

Firmware and PCB now release on separate tag prefixes with separate CHANGELOGs. v*.*.* tags cut a firmware release (CHANGELOG.md, no asset). pcb-*.*.* tags cut a PCB release (pcb/CHANGELOG.md, gerber.zip attached, makeLatest: false). The unified release.yml and its conditional "PCB inchangé" body note are retired.

The split eliminates the forced shared lifecycle: a PCB revision no longer requires a firmware bump (and vice versa), and release notes for either component no longer pull in commits from the other.

What changed

  • New: .github/workflows/release-firmware.ymlv*.*.* trigger, no asset, makeLatest: legacy, writes CHANGELOG.md.
  • New: .github/workflows/release-pcb.ymlpcb-*.*.* trigger, gerber.zip validated via Garde C12, makeLatest: false, writes pcb/CHANGELOG.md.
  • Removed: .github/workflows/release.yml — unified workflow + the PCB-unchanged conditional body note.
  • Updated: README.md "Couper une release" split into two runbooks with an explicit seed-tag guardrail.
  • Seeded: annotated tag pcb-0.1.0 on b5573bb (the squash-merge where pcb/ landed on main) as the anchor for git tag --list 'pcb-*'. First real PCB release will be pcb-0.1.1.

Key design decisions

  • fromTag/toTag with a shell pre-step, not the action's tag: mode. requarks/changelog-action's default mode runs a prefix-blind git describe — on a pcb-0.1.1 push it would return the latest firmware tag, producing either an action failure (latestTag.name !== tag) or a PCB changelog full of firmware commits. The pre-step computes the component-scoped previous tag with git tag --list '<prefix>[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | grep -v -Fx "${ref}" | head -n1 and fails loudly on empty. Skip-by-name is correct under forward-looking retags of older versions; positional sed '2p' would not be.
  • Shared concurrency: release-main across both workflows. Per-workflow groups would only serialize retags of the same component. The real concern is a combined git push origin main v0.2.0 pcb-0.1.1 racing two CHANGELOG auto-commits against main. A shared group closes that window.
  • Refname-exact tag-on-main guard. grep 'origin/main$'git branch -r --format='%(refname)' | grep -Fxq 'refs/remotes/origin/main'. Closes a residual risk from the hardening learning doc — origin/main$ would match feat-main or a second remote's main.
  • Seed tag anchors on b5573bb, not on the original import commit 7ea5eab. PR feat: fusionner frangipool/pcb dans le monorepo + release workflow #7 was squash-merged, so 7ea5eab is unreachable from main. git merge-base pcb-0.1.0 HEAD would otherwise return c73abbf and pollute the first PCB release notes with the monorepo fusion itself. b5573bb — the squash-merge — is the correct anchor for a post-monorepo PCB lifecycle.
  • No composite action for shared guards. ~20 duplicated lines between the two workflows. Extracting them would recreate the coupling the split was designed to remove.

Known residual risks

Risk Status
TOCTOU on auto-commit if a human push hits main between checkout and git-auto-commit-action Shared concurrency closes the workflow-vs-workflow window; human-vs-workflow window fails cleanly (no half-published release)
No pre-merge actionlint — new workflow YAML is first validated on a real tag push Tracked as follow-up; would have caught three findings during this PR's code review
No workflow_dispatch dry-run path Deferred; revisit after first production use if pain emerges
Seed tag pcb-0.1.0 deletion would break all future PCB releases Workflow-level guard (if: github.ref_name != 'pcb-0.1.0') + README "NE JAMAIS supprimer" encart. Repo-level tag protection rule recommended but not yet configured

Post-Deploy Monitoring & Validation

No CI-level monitoring to configure — the GitHub Actions tab is sufficient. The first real release cycle is the end-to-end test:

  • Firmware (v0.2.0): release-firmware.yml only fires (not release-pcb.yml). CHANGELOG.md gets a new ## [v0.2.0] entry excluding scopes pcb,changelog and types chore,style,build. No asset attached. "Latest release" badge updates.
  • PCB (pcb-0.1.1): release-pcb.yml only fires. pcb/CHANGELOG.md gets a new ## [pcb-0.1.1] entry above the archived ## [v0.1.0] - 2023-06-08. gerber.zip attached, C12 pattern passes. "Latest release" badge unchanged — stays on firmware.
  • Combined push (git push origin main v0.2.1 pcb-0.1.2): both workflows queue on release-main, serialize cleanly. Both releases land without non-fast-forward errors.

Full post-merge checklist in docs/plans/2026-04-23-001-refactor-split-release-tags-plan.md under "Post-merge validation checklist". Failure signal: an unexpected asset on a firmware release, missing asset on a PCB release, or a CHANGELOG commit without a corresponding GitHub Release (commit-before-publish drift). Rollback: gh release delete <tag> --yes, git tag -d <tag>, git push origin :refs/tags/<tag>, re-tag after fixing the workflow.

References

  • Origin: docs/brainstorms/2026-04-23-001-split-release-tags-requirements.md
  • Plan: docs/plans/2026-04-23-001-refactor-split-release-tags-plan.md
  • Hardening learning: docs/solutions/architecture/tag-triggered-release-workflow-hardening.md
  • Code review artifact: .context/compound-engineering/ce-code-review/20260423-072851-f135194c/ — 4 safe_auto applied; P1 seed-tag retarget resolved during review; 2 P2 residuals tracked above

Compound Engineering
Claude Code

gaetanars and others added 3 commits April 23, 2026 07:22
- docs/brainstorms/2026-04-23-001-split-release-tags-requirements.md:
  requirements for splitting the unified release workflow into two
  tag-triggered streams (firmware on v*.*.*, PCB on pcb-*.*.*) with
  separate CHANGELOGs and a shared concurrency group.
- docs/plans/2026-04-23-001-refactor-split-release-tags-plan.md:
  implementation plan covering 5 units from seed tag push through
  README runbook update, with fromTag/toTag + grep -v -Fx prev_tag
  computation, refname-exact tag-on-main guard, and seed-tag guardrails.
Replace the single tag-triggered release.yml (v*.*.*) with two mirror
workflows keyed by tag prefix, each writing to its own CHANGELOG and
publishing the asset profile that matches its component.

- .github/workflows/release-firmware.yml — tags v*.*.*. Writes
  CHANGELOG.md with excludeScopes: changelog,pcb and excludeTypes:
  chore,style,build. No asset attached. makeLatest: legacy (unchanged).
- .github/workflows/release-pcb.yml — tags pcb-*.*.*. Writes
  pcb/CHANGELOG.md. Zips pcb/src into gerber.zip with Garde C12
  validation. makeLatest: false so the PCB release never shadows the
  firmware 'latest' badge. Job-level guard 'if: github.ref_name !=
  pcb-0.1.0' makes the workflow idempotent to the seed baseline.
- .github/workflows/release.yml removed — the conditional 'PCB
  inchangé depuis X' note disappears with it.
- README 'Couper une release' split into two procedures (firmware and
  PCB), each documenting regex, tag-push steps, and forward-looking
  retag. Explicit guardrail that pcb-0.1.0 is an immutable seed
  baseline and must never be deleted, moved, or retagged.

Both workflows:
- Use requarks/changelog-action in fromTag/toTag mode with the
  previous tag precomputed via 'git tag --list <prefix>* --sort=-v:refname
  | grep -v -Fx <current> | head -n1' (skip-by-name, not by sort
  position, so non-monotonic retags stay correct). Guard empty result
  loudly instead of falling through to silent malformed release.
- Share 'concurrency: group: release-main' so a combined push
  'v0.2.0 pcb-0.1.1' serialises cleanly on the main auto-commit.
- Upgrade the tag-on-main guard to refname-exact match (closes a
  residual risk from the hardening learning doc's 'origin/main$'
  suffix-match fragility).

The seed baseline tag pcb-0.1.0 was pushed separately to 7ea5eab
before this commit lands; the PCB workflow depends on its presence
to compute prev_tag for pcb-0.1.1 onwards.

See docs/plans/2026-04-23-001-refactor-split-release-tags-plan.md
for implementation units and docs/brainstorms/2026-04-23-001-split-release-tags-requirements.md
for the product decisions this plan implements.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Applied from the ce-code-review pass on this branch
(.context/compound-engineering/ce-code-review/20260423-072851-f135194c/):

Safe-auto fixes:
- Tightened tag-list globs in both workflows to match the trigger regex
  quantifier (v[0-9][0-9]*... instead of v[0-9]*..., and pcb- analogue).
  Prevents a malformed historical tag like v.1.0 from ever being selected
  as prev_tag.
- Made the Gerber validation grep case-insensitive. Protects against a
  future EDA toolchain export using lowercase extensions.
- Clarified the README PCB section: gerber.zip is now attached only to
  pcb-* releases, never firmware.

P1 fix (seed-tag baseline):
- Retargeted the pcb-0.1.0 seed tag from 7ea5eab to b5573bb. The original
  import commit 7ea5eab was squash-merged in PR #7 and is unreachable
  from main; git merge-base pcb-0.1.0 HEAD returned c73abbf, meaning
  the first pcb-0.1.1 release would have pulled 6 non-PCB commits
  (monorepo fusion, docs, gitignore, ci-split itself) into the release
  notes. The squash-merge commit b5573bb is the first reachable commit
  on main that contains pcb/ and is the correct baseline anchor.
- Updated README seed-tag guardrail + plan references to cite b5573bb
  rather than 7ea5eab, with a one-line note explaining why.

Residual P2 findings documented in the run artifact (not blocking):
- TOCTOU on auto-commit (concurrency group serializes job start, not
  push vs main). Commit-before-publish ordering means the failure mode
  is clean (no half-published release), recoverable by retag.
- No actionlint in CI. Scope creep vs this plan; track as follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@gaetanars gaetanars merged commit a85ced5 into main Apr 23, 2026
8 checks passed
@gaetanars gaetanars deleted the refactor/split-release-tags branch April 23, 2026 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant