Skip to content

refactor: Shared workflows v1 — opt-in packaging, one repo, pinned major tag #49

Description

@mairas

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/pr-checks.yml — tests, version-check (optional action), lintian (self-skips without build-deb action; hardcodes ubuntu-latest), nested ./.github/workflows/version-bump-check.yml.
  • .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.
  • Callers' main.yml already sets concurrency (checked cockpit-apt, skip, HALPI2-blinkenlights).

External References

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.
    • build-deb.yml — changelog, repo-local build-deb action, 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 (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.

Caller composition per repo kind:

Caller file deb repo npm repo tag-only repo (docs-tools) deb + npm (signalk-halpi)
pr.yml checks, build-deb (check mode), status checks, status checks, status checks, build-deb (check mode), status
main.yml tests?, release-version → build-deb → stage-release → apt-publish unstable tests?, release-version → stage-release tests?, release-version → stage-release tests?, release-version → build-deb → stage-release → apt-publish unstable
release.yml apt-publish stable publish-npm none apt-publish stable, publish-npm
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]
Loading

Implementation Units

System-Wide Impact

  • 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)
  • Issues: 29, 41, 48; hatlabs 2
  • PR 47 (caller-owned check name); docs-tools PR 31 (in-flight migration to legacy workflows)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions