You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Target repo: halos-org/shared-workflows. Migration touches 32 caller repos in halos-org and hatlabs.
Overview
Redesign the caller interface of the shared reusable workflows while every caller is edited once:
Packaging is composed by the caller, not switched off by flags. A deb repo calls a deb build; an npm repo does not.
hatlabs/shared-workflows merges into halos-org/shared-workflows and is retired.
Callers pin a moving major tag @v1 instead of @main.
Each caller owns the name of its required check through a local aggregating job.
npm publishing becomes a shared workflow.
Problem Frame
See origin note for the inventory. In short:
build-release.yml builds a deb unless told not to (build-deb: true default); 6 repos opt out. package-name is required and described as a Debian name even without a deb.
Lintian flags point in opposite directions (skip-lintian in pr-checks, run-lintian in build-release). The pr-checks lintian job ignores runs-on.
Two copies of the workflows have drifted in both directions; the only intended difference is the apt-repository default. Release notes hardcode apt.halos.fi (hatlabs issue 2).
Every caller uses @main, so any breaking change hits all 32 repos at merge time.
Required check names are checks / lintian, checks / version-bump-check / version-bump-check, and so on. A job rename in this repo blocks every PR in every caller. Required-check sets are also inconsistent across repos.
Five npm release.yml copies have diverged (issue 29).
Requirements Trace
R1. No workflow builds or checks a deb unless the caller calls a deb-specific workflow.
R2. One source repo. halos-org carries no hatlabs-only defaults or logic; hatlabs callers pass generic inputs.
R3. Callers reference @v1. Breaking changes ship as v2, not on main.
R4. Each caller's branch protection requires one caller-owned check name; renaming a job in this repo does not change it.
R5. npm publishing runs from a shared workflow; the five callers delete their copies.
R6. No caller is broken at any point during the migration. Each non-canary caller migrates in one PR; canaries take two (rc pin, then @v1).
R7. Release artifacts (tags, pre-releases, drafts, .deb assets, APT dispatches, npm publishes) are equivalent before and after migration.
Scope Boundaries
Out: internal-only fixes that need no caller edits (issue 30, issue 37 and PR 31, issue 23, translation-status issues 40, 43, 45, 46), except where a rewritten file absorbs them (issue 48, see decisions).
Out: redesign of check-image-updates.yml and translation-status.yml. Their callers only change the ref to @v1.
Out: sharing repo-local actions (run-tests, build-deb, check-versions). They stay per repo.
Out: branch protection for the two private repos without protection (signalk-questdb-history-provider-archive, signalk-n2k-playback). Plan availability not checked.
Out: SHA-pinning third-party actions.
Context & Research
Relevant Code and Patterns
.github/workflows/build-release.yml — revision calculation (reads VERSION, next +N from tags), tag derivation, pre-release and draft creation, deb steps gated by inputs.build-deb. No job or workflow outputs exist today.
.github/workflows/publish-stable.yml — tag parse, fails with zero assets, APT dispatch channel: stable.
.github/workflows/check-image-updates.yml — checks out its own scripts with github.job_workflow_sha, which does not exist and evaluates empty, so scripts always come from main (issue 58).
examples/docs-repo/.github/workflows/translation-status.yml and README.md "Making it a gate" — the caller-owned status job pattern from PR 47.
Five npm release.yml copies (skip, skip-freeboard-panel, signalk-duckdb-history-provider, signalk-questdb-history-provider, signalk-halpi). Common core: release: published, non-prerelease only, id-token: write trusted publishing, npm 11 via corepack, skip if version already on npm. Divergence: VERSION vs package.json guard (2 of 5), persist-credentials: false (1), concurrency (4), package name hardcoded (4) vs read from package.json (1), explicit build step (1).
Repo state: zero tags, zero releases, no CI of its own, no AGENTS.md, README input tables stale, examples/cockpit-apt references inputs that do not exist. main has protection with no required checks. probe.yml and guardprobe.yml still listed as active workflows from deleted test branches.
Cross-org callers must pass secrets explicitly (secrets: inherit is same-org only).
Key Technical Decisions
New filenames, legacy files frozen. New workflows get new names. build-release.yml, pr-checks.yml, publish-stable.yml stay untouched on main until the last caller leaves @main, then are deleted. This satisfies R6 with one PR per caller. Rejected: reuse names and first move every caller to a frozen @v0 tag — doubles caller PRs.
Legacy files are not part of the v1 interface.v1.0.0 contains them only because main still serves @main callers. AGENTS.md and the README migration guide say so, and their removal in v1.1.0 is the one documented exception to "breaking changes need a new major". Rejected: cutting v1 from a release/v1 branch without the legacy files — a second long-lived branch to keep in sync for the whole migration.
The first major is v1. No tags exist. Full releases v1.0.0, v1.1.0, … plus a moving v1 tag, per the actions/toolkit process. Release candidates (v1.0.0-rc.N) serve canary callers before v1 exists.
Release pipeline as composed jobs. The ordering is fixed by data: the deb build needs the version, the release needs the deb, the APT dispatch needs the release asset. Each step is one reusable workflow that exposes outputs:
release-version.yml — reads VERSION, computes +N, derives tags, reports whether a release is due. Generic.
stage-release.yml — pre-release and draft, attaching any artifact it is given. Generic.
apt-publish.yml — APT dispatch for a channel (unstable from main, stable from release). Deb only. Replaces the dispatch in build-release and publish-stable.yml.
publish-npm.yml — npm only.
Rejected: one release workflow with a deb flag — that is the default-on flag inverted, which R1 exists to remove.
PR checks split the same way.checks.yml runs tests, version-check, version-bump-check. Deb repos add a call to build-deb.yml in check mode (build and lintian, no version inputs, no upload). skip-lintian disappears; lintian is an input of build-deb.yml (default on) for the two repos that disable it. The runs-on input applies to every job.
skip-tests disappears. Tests on main are a job the caller includes or omits. Branding repos simply omit it.
apt-repository becomes required on apt-publish.yml. No default in either org's favour. Release-note install text derives the APT host from the repository name (both repos are named after their domain) and the distro and component from the existing inputs, fixing hatlabs issue 2 without hatlabs logic. Whether the keyring filename and key URL path are the same on both hosts is checked on the hosts in Unit 3; if not, they become inputs.
Caller-owned check. Every caller's pr.yml ends with a status job that needs every other job in the file, shared or local (halos-pi-gen image-version-check, flash-live-system build and shellcheck stay required). if: always(); it fails unless each result is success. On PRs no job is expected to skip, so skipped fails too; a caller that makes a job conditional lists that job as allowed to skip. Branch protection requires status only. Same shape as PR 47.
Tags target the tested commit (issue 48).stage-release.yml is new code, so it passes the run's SHA as the tag target. Absorbed here because the file is rewritten anyway.
Releases only from the default branch. Because tags now target the run's SHA, a workflow_dispatch of main.yml on a feature branch would tag and dispatch that branch. release-version.yml fails with a clear error when the ref is not the default branch. Legacy behaviour tagged main's head from any branch, which was also wrong.
Publish workflows skip pre-releases.apt-publish.yml stable mode and publish-npm.yml both skip when github.event.release.prerelease is true. Today pre-releases are created with the job token and do not trigger release.yml; the guard keeps that true if a token change ever makes them trigger.
npm trusted publishing keeps working. npm validates the calling workflow's filename, so callers keep .github/workflows/release.yml. publish-npm.yml reads the package name from package.json, enforces VERSION equals package.json version, enforces the release tag's upstream part equals VERSION at the checked-out commit, sets persist-credentials: false, and skips when the version already exists. The tag guard catches a legacy draft (created without a tag target) published after main moved to a new VERSION. The caller grants id-token: write.
hatlabs retirement. Port the only hatlabs-ahead change (.coderabbit.ya?ml exclusion in version-bump-check). hatlabs callers switch to halos-org/shared-workflows@v1. Archive hatlabs/shared-workflows after its last caller moves; archiving is a repo setting and needs approval at that moment.
Open Questions
Resolved During Planning
Does trusted publishing allow the publish step in another org's reusable workflow? Yes; npm checks the caller's filename.
Do version-bump-check.yml, check-image-updates.yml, translation-status.yml change interface? No. Their callers only change the ref.
Do caller migration PRs need a VERSION bump? No. .github/ is excluded by version-bump-check.
Deferred to Implementation
Nested ./ reference under a tag pin. GitHub does not document whether uses: ./.github/workflows/version-bump-check.yml inside a called workflow resolves at the caller's pinned tag. Verify with an rc tag on a canary. If it does not, reference halos-org/shared-workflows/.github/workflows/version-bump-check.yml@v1 explicitly and accept updating it per major.
How build-deb.yml check mode supplies a version to the changelog step without release inputs.
Whether release-version.yml and stage-release.yml need a guard against a tag created between the two jobs; caller concurrency covers same-repo runs.
Exact output names of release-version.yml.
High-Level Technical Design
This illustrates the intended approach and is directional guidance for review, not implementation specification. The implementing agent should treat it as context, not code to reproduce.
flowchart LR
T[tests local job, optional] --> V[release-version]
V -->|release due| B[build-deb]
B -->|artifact| C[stage-release]
V -->|npm/tag-only| C
C --> A[apt-publish unstable]
Interaction graph: apt.halos.fi and apt.hatlabs.fi receive package-updated dispatches; payload must stay identical. npm registry trusted-publisher config binds to caller filename release.yml.
Error propagation: a failing shared job fails the caller's status; skipped jobs pass.
State lifecycle risks: open PRs in a repo lack status after its protection changes; they need a rebase. Draft cleanup deletes all drafts in the caller repo, unchanged.
API surface parity:version-bump-check.yml, check-image-updates.yml, translation-status.yml keep their interface.
Unchanged invariants: tag format v<upstream>+<N> and _pre suffix; N = max over existing v<upstream>+* git tags (including _pre, full fetch) + 1; Debian version <upstream>-<N>; .deb rename suffix; repo-local action contract; APT dispatch payload.
Rollback
One caller, before Unit 8: revert the migration PR and restore the old required check names in the same step. The protection change needs approval together with the revert.
Legacy files stay frozen under every tag, so build-release.yml@v1.0.0 behaves exactly like today's @main.
After Unit 8: rollback means pinning the legacy files at @v1.0.0; hatlabs repos then pass apt-repository: hatlabs/apt.hatlabs.fi explicitly.
Risks & Dependencies
Risk
Mitigation
Nested ./ call does not follow the pinned tag
Verified on rc canary; fallback is an explicit @v1 reference
APT dispatch payload differs and the APT repo ignores or misfiles a package
Unit 3 compares canary payload and resulting APT entry with the previous release
Branch protection change blocks in-flight PRs in a repo
Change protection only when merging that repo's migration PR; list open PRs first
A caller's legacy @main run breaks during migration
Legacy files are frozen, not edited, until Unit 8
Moving v1 pushes a regression to all callers at once
Changes land as v1.x.0 releases; canaries can pin vX.Y.Z before v1 moves
npm trusted publishing rejects the shared workflow
Unit 4 canary publishes before other npm repos migrate; NPM_TOKEN fallback not added unless needed
Documentation / Operational Notes
Workspace AGENTS.md (halos) describes shared-workflows usage and the per-org copies; update it after Unit 8.
docs/solutions or memory entry: caller-owned status check pattern and the tag-pin rule.
Sources & References
Origin: caller inventory of 2026-09-17 (local explore note)
Target repo: halos-org/shared-workflows. Migration touches 32 caller repos in halos-org and hatlabs.
Overview
Redesign the caller interface of the shared reusable workflows while every caller is edited once:
hatlabs/shared-workflowsmerges intohalos-org/shared-workflowsand is retired.@v1instead of@main.Problem Frame
See origin note for the inventory. In short:
build-release.ymlbuilds a deb unless told not to (build-deb: truedefault); 6 repos opt out.package-nameis required and described as a Debian name even without a deb.skip-lintianin pr-checks,run-lintianin build-release). The pr-checks lintian job ignoresruns-on.apt-repositorydefault. Release notes hardcodeapt.halos.fi(hatlabs issue 2).@main, so any breaking change hits all 32 repos at merge time.checks / lintian,checks / version-bump-check / version-bump-check, and so on. A job rename in this repo blocks every PR in every caller. Required-check sets are also inconsistent across repos.release.ymlcopies have diverged (issue 29).Requirements Trace
@v1. Breaking changes ship asv2, not onmain.@v1)..debassets, APT dispatches, npm publishes) are equivalent before and after migration.Scope Boundaries
check-image-updates.ymlandtranslation-status.yml. Their callers only change the ref to@v1.run-tests,build-deb,check-versions). They stay per repo.Context & Research
Relevant Code and Patterns
.github/workflows/build-release.yml— revision calculation (readsVERSION, next+Nfrom tags), tag derivation, pre-release and draft creation, deb steps gated byinputs.build-deb. No job or workflow outputs exist today..github/workflows/pr-checks.yml— tests, version-check (optional action), lintian (self-skips withoutbuild-debaction; hardcodesubuntu-latest), nested./.github/workflows/version-bump-check.yml..github/workflows/publish-stable.yml— tag parse, fails with zero assets, APT dispatchchannel: stable..github/workflows/check-image-updates.yml— checks out its own scripts withgithub.job_workflow_sha, which does not exist and evaluates empty, so scripts always come frommain(issue 58).examples/docs-repo/.github/workflows/translation-status.ymlandREADME.md"Making it a gate" — the caller-ownedstatusjob pattern from PR 47.release.ymlcopies (skip, skip-freeboard-panel, signalk-duckdb-history-provider, signalk-questdb-history-provider, signalk-halpi). Common core:release: published, non-prerelease only,id-token: writetrusted publishing, npm 11 via corepack, skip if version already on npm. Divergence: VERSION vs package.json guard (2 of 5),persist-credentials: false(1), concurrency (4), package name hardcoded (4) vs read from package.json (1), explicit build step (1).examples/cockpit-aptreferences inputs that do not exist.mainhas protection with no required checks.probe.ymlandguardprobe.ymlstill listed as active workflows from deleted test branches.main.ymlalready setsconcurrency(checked cockpit-apt, skip, HALPI2-blinkenlights).External References
<job> / <reusable job>: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/troubleshooting-rulesid-token: writeneeded in caller and called workflow: https://docs.npmjs.com/trusted-publishers/secrets: inheritis same-org only).Key Technical Decisions
build-release.yml,pr-checks.yml,publish-stable.ymlstay untouched onmainuntil the last caller leaves@main, then are deleted. This satisfies R6 with one PR per caller. Rejected: reuse names and first move every caller to a frozen@v0tag — doubles caller PRs.v1.0.0contains them only becausemainstill serves@maincallers. AGENTS.md and the README migration guide say so, and their removal inv1.1.0is the one documented exception to "breaking changes need a new major". Rejected: cutting v1 from arelease/v1branch without the legacy files — a second long-lived branch to keep in sync for the whole migration.v1. No tags exist. Full releasesv1.0.0,v1.1.0, … plus a movingv1tag, per the actions/toolkit process. Release candidates (v1.0.0-rc.N) serve canary callers beforev1exists.release-version.yml— reads VERSION, computes+N, derives tags, reports whether a release is due. Generic.build-deb.yml— changelog, repo-localbuild-debaction, lintian, rename, upload artifact. Deb only.stage-release.yml— pre-release and draft, attaching any artifact it is given. Generic.apt-publish.yml— APT dispatch for a channel (unstablefrom main,stablefrom release). Deb only. Replaces the dispatch in build-release andpublish-stable.yml.publish-npm.yml— npm only.Rejected: one release workflow with a
debflag — that is the default-on flag inverted, which R1 exists to remove.checks.ymlruns tests, version-check, version-bump-check. Deb repos add a call tobuild-deb.ymlin check mode (build and lintian, no version inputs, no upload).skip-lintiandisappears;lintianis an input ofbuild-deb.yml(default on) for the two repos that disable it. Theruns-oninput applies to every job.skip-testsdisappears. Tests on main are a job the caller includes or omits. Branding repos simply omit it.apt-repositorybecomes required onapt-publish.yml. No default in either org's favour. Release-note install text derives the APT host from the repository name (both repos are named after their domain) and the distro and component from the existing inputs, fixing hatlabs issue 2 without hatlabs logic. Whether the keyring filename and key URL path are the same on both hosts is checked on the hosts in Unit 3; if not, they become inputs.pr.ymlends with astatusjob thatneedsevery other job in the file, shared or local (halos-pi-genimage-version-check, flash-live-systembuildandshellcheckstay required).if: always(); it fails unless each result issuccess. On PRs no job is expected to skip, soskippedfails too; a caller that makes a job conditional lists that job as allowed to skip. Branch protection requiresstatusonly. Same shape as PR 47.stage-release.ymlis new code, so it passes the run's SHA as the tag target. Absorbed here because the file is rewritten anyway.workflow_dispatchofmain.ymlon a feature branch would tag and dispatch that branch.release-version.ymlfails with a clear error when the ref is not the default branch. Legacy behaviour tagged main's head from any branch, which was also wrong.apt-publish.ymlstable mode andpublish-npm.ymlboth skip whengithub.event.release.prereleaseis true. Today pre-releases are created with the job token and do not triggerrelease.yml; the guard keeps that true if a token change ever makes them trigger..github/workflows/release.yml.publish-npm.ymlreads the package name frompackage.json, enforces VERSION equals package.json version, enforces the release tag's upstream part equals VERSION at the checked-out commit, setspersist-credentials: false, and skips when the version already exists. The tag guard catches a legacy draft (created without a tag target) published aftermainmoved to a new VERSION. The caller grantsid-token: write..coderabbit.ya?mlexclusion in version-bump-check). hatlabs callers switch tohalos-org/shared-workflows@v1. Archivehatlabs/shared-workflowsafter its last caller moves; archiving is a repo setting and needs approval at that moment.Open Questions
Resolved During Planning
version-bump-check.yml,check-image-updates.yml,translation-status.ymlchange interface? No. Their callers only change the ref..github/is excluded by version-bump-check.Deferred to Implementation
./reference under a tag pin. GitHub does not document whetheruses: ./.github/workflows/version-bump-check.ymlinside a called workflow resolves at the caller's pinned tag. Verify with an rc tag on a canary. If it does not, referencehalos-org/shared-workflows/.github/workflows/version-bump-check.yml@v1explicitly and accept updating it per major.build-deb.ymlcheck mode supplies a version to the changelog step without release inputs.release-version.ymlandstage-release.ymlneed a guard against a tag created between the two jobs; callerconcurrencycovers same-repo runs.release-version.yml.High-Level Technical Design
Caller composition per repo kind:
pr.ymlmain.ymlrelease.ymlImplementation Units
checks.yml,build-deb.ymlcheck mode #51release-version.yml,build-deb.ymlrelease mode,stage-release.yml,apt-publish.yml#52publish-npm.yml#53System-Wide Impact
package-updateddispatches; payload must stay identical. npm registry trusted-publisher config binds to caller filenamerelease.yml.status; skipped jobs pass.statusafter its protection changes; they need a rebase. Draft cleanup deletes all drafts in the caller repo, unchanged.version-bump-check.yml,check-image-updates.yml,translation-status.ymlkeep their interface.v<upstream>+<N>and_presuffix; N = max over existingv<upstream>+*git tags (including_pre, full fetch) + 1; Debian version<upstream>-<N>;.debrename suffix; repo-local action contract; APT dispatch payload.Rollback
build-release.yml@v1.0.0behaves exactly like today's@main.@v1.0.0; hatlabs repos then passapt-repository: hatlabs/apt.hatlabs.fiexplicitly.Risks & Dependencies
./call does not follow the pinned tag@v1reference@mainrun breaks during migrationv1pushes a regression to all callers at oncev1.x.0releases; canaries can pinvX.Y.Zbeforev1movesDocumentation / Operational Notes
AGENTS.md(halos) describesshared-workflowsusage and the per-org copies; update it after Unit 8.docs/solutionsor memory entry: caller-ownedstatuscheck pattern and the tag-pin rule.Sources & References