Skip to content

feat: auto-sync add-on pins to upstream FTW releases and publish beta - #4

Merged
frahlg merged 2 commits into
srcfl:mainfrom
HuggeK:worktree-auto-ftw-sync
Aug 7, 2026
Merged

frahlg merged 2 commits into
srcfl:mainfrom
HuggeK:worktree-auto-ftw-sync

Conversation

@HuggeK

@HuggeK HuggeK commented Jul 29, 2026 •

Copy link
Copy Markdown
Contributor

What this does

Makes the FTW Home Assistant add-on update itself when a new FTW version is
released upstream
and get packaged on the beta channel — no hand-editing of
pins. Today every release is manually dispatched and every upstream digest is
pinned by hand in compatibility.yaml; this closes that loop while keeping the
existing fail-closed release gates as the only path to a published artifact.

The flow

srcfl/ftw release ──▶ Sync upstream FTW ──▶ PR ──▶ checks ──▶ merge to main
                                                                    │
                                                                    ▼
                                                          Auto publish beta
                                                                    │
                                                                    ▼
                                                Publish beta (existing, signed)
  1. Sync upstream FTW (.github/workflows/sync-upstream.yml) — runs
    hourly, on repository_dispatch: ftw-release, or manually. It runs
    scripts/upstream_sync.py, which picks the highest published upstream
    Core/Optimizer release, resolves the multi-arch digest and source commit
    from the registry, and rewrites compatibility.yaml, ftw/config.yaml,
    ftw/CHANGELOG.md, and a fresh blocked pilot record. It then validates,
    runs the test suite, opens a bot PR, runs check.yml on the exact head,
    checks that main has not moved, merges that head, deletes its branch,
    and nudges publication.
  2. Auto publish beta (.github/workflows/auto-publish-beta.yml) — on the
    push to main, dispatches the existing release-beta.yml only when
    config.yaml still carries the pending beta marker, guarding against
    already-existing tags/releases and active runs.

Why it's still safe

The automation holds no packages: write and never builds, pushes, or
retags an image. It only produces a bot PR and waits for the exact branch check before
merging it. It then dispatches the existing signed release-beta.yml, which
independently re-verifies the version match, publisher marker, source-on-main, cosign
signatures, and SBOMs. The fail-closed gates remain the only way to ship.

Design details & guarantees
  • Pilot evidence stays fail-closed: a new Core release must link a labeled
    live-pilot PR or issue comment, or a live-pilot Actions run. Without one,
    the sync writes nothing and retries later. A release page is not evidence.
  • Forward-only: upstream_sync.py never downgrades and skips upstream
    releases whose multi-arch image is not fully published yet (ImageNotReady
    → retry on the next schedule tick).
  • Label re-check: before pinning, it asserts the image's
    org.opencontainers.image.version / .revision labels match the release, so
    it can't pin a mislabeled or half-published image.
  • HA qualification stays blocked: each sync writes a blocked pilot record
    and resets home_assistant_os_supervisor to blocked +
    promoted_from_beta to nulls, so nothing is auto-promoted to stable.
  • Idempotent versioning: the add-on beta number is bumped past any tag that
    already exists.
  • Layered triggers: Auto publish beta fires on the merge push and on a
    schedule fallback, so a missed dispatch still self-heals.
  • Optional upstream hook: srcfl/ftw can send repository_dispatch type
    ftw-release for instant syncs; the hourly cron is the fallback if it
    doesn't.

Tests

python -m unittest discover -s tests → 118 passing. Adds
tests/test_upstream_sync.py (version ordering, release selection, next-version
logic, file rewrites, and a compose→YAML round-trip asserted against the real
validators) and tests/test_automation_workflows.py (structure assertions that
the workflows can't write packages or build/push images). Generalizes the
version-coupled tests in test_validate.py / test_release_gate.py so future
automated bumps don't break the suite.

🤖 Generated with Claude Code

Add end-to-end automation so a new upstream FTW Core/Optimizer release
flows into a packaged beta add-on without hand-editing pins:

- scripts/upstream_sync.py selects the highest published upstream release,
  resolves the multi-arch digest and source commit from the registry,
  verifies the version/revision image labels the release gate re-checks,
  and rewrites compatibility.yaml, ftw/config.yaml, ftw/CHANGELOG.md and a
  fresh blocked pilot record. It only moves forward and retries later when
  an image is not fully published (ImageNotReady).
- .github/workflows/sync-upstream.yml runs hourly, on repository_dispatch
  (ftw-release), or manually; it validates + tests, opens a PR, runs
  check.yml on the branch, merges when checks pass, then nudges publication.
- .github/workflows/auto-publish-beta.yml dispatches the existing signed
  release-beta.yml only when config.yaml carries a pending beta marker,
  guarding against existing tags/releases and active runs.

The automation holds no packages:write and never builds, pushes, or retags
images, so the fail-closed gates (human-reviewed main, cosign signing,
SBOMs, pilot qualification) remain the only path to a published release.
Generalizes version-coupled tests to read the pinned version, and adds
unit + workflow-structure coverage for the new automation.

Co-authored-by: HuggeK <48095810+HuggeK@users.noreply.github.com>
@HuggeK
HuggeK marked this pull request as ready for review July 29, 2026 13:54
@HuggeK
HuggeK requested a review from frahlg as a code owner July 29, 2026 13:54

@miravoss26 miravoss26 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid automation PR. Two scheduled workflows (sync-upstream, auto-publish-beta) plus upstream_sync.py and a real test suite; all five checks green.

Security screen:

  • Permissions scoped top-level contents: read, elevated per-job only where needed.
  • auto-publish-beta: the VERSION that flows into the shell is regex-validated (x.y.z-beta.N) and the dispatch step is gated on it, so config.yaml is not an injection surface. checkout uses persist-credentials: false.
  • sync-upstream: forward-only, resolves image digests from ghcr, and gates the pin PR + auto-merge on validate.py + the unit tests + check.yml.

One thing worth a conscious yes from a human (not a bug): sync-upstream self-merges its own pin PRs to main when checks pass (gh pr merge --squash --auto, contents: write). That is the right shape for a downstream mirror, but it means main can advance without a human in the loop, so it is your call to accept.

Reads safe to me; the self-merge is the only human-decision point. Not on my auto-merge allowlist regardless, so a human takes the merge.

@miravoss26 miravoss26 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closes the manual-pin loop: two new workflows (sync-upstream, auto-publish-beta) + scripts/upstream_sync.py + tests auto-pin the add-on to the latest upstream FTW release and publish it on beta.

Security screen, clean:

  • Top-level permissions: contents: read, per-job elevation only where needed.
  • persist-credentials: false on checkout; GITHUB_TOKEN only, no PATs.
  • version / title / branch passed through env: and referenced as ${VAR}, not inline ${{ }} in run blocks, so no shell-injection surface; heredocs are quoted.
  • upstream_sync.py uses list-form subprocess (no shell=True) and strict regexes for versions/digests/commits; forward-only, skips not-yet-published images.

One human decision, not a bug: the sync job opens a PR and then auto-merges it (gh pr merge --auto, fallback watch+merge), gated only by check.yml + branch protection, so the add-on self-updates with no human in the merge loop, and the job holds contents: write + pull-requests: write. If that trust model is intended (the description says it is), this is safe to merge from my read. Worth confirming branch protection on main actually requires check.yml to pass, since that gate is now load-bearing.

@HuggeK

HuggeK commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@frahlg Tänkte se om jag kunde testa detta idag om vi ska cutta en ny release i beta eller release? Skulle du kunna kika på detta?

@HuggeK

HuggeK commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

Likaså #5

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@frahlg

frahlg commented Aug 7, 2026

Copy link
Copy Markdown
Member

Final security and release pass on head 525dc03:

  • main has no branch protection and no ruleset. The old --auto path therefore could not serve as the promised check gate. The workflow now waits for the exact check run, matches the checked head SHA, requires main to remain on the tested base, and only then merges.
  • Bot pin branches now use signed-off commits and are deleted on merge.
  • The dispatcher now has its own concurrency group, so it cannot hold or replace a pending beta publication or finalizer. It also checks both workflows before dispatch.
  • Both scheduled workflows stop on forks and only run in srcfl/home-assistant-addons.
  • A Core release page is no longer accepted as live-pilot evidence. The release must contain a labeled PR or issue comment, or an Actions run. Without it the sync writes nothing and retries later.
  • A live run against current v1.16.1-beta.10 returned changed=false because that release lacks labeled pilot evidence. The worktree diff hash was unchanged before and after the run.
  • Local validation, compileall, and all 118 unit tests pass. GitHub run 31180638559 passes all five jobs, including amd64 and aarch64 build and smoke.
  • There are no inline review threads.

The automation will stay idle until the upstream release carries real pilot evidence. With that fail-closed behavior, this is safe to squash-merge.

@frahlg

frahlg commented Aug 7, 2026

Copy link
Copy Markdown
Member

Merge is intentionally paused for one owner decision. This PR installs standing workflows with contents: write, pull-requests: write, and actions: write; after the hardening they can still self-merge checked pin PRs and dispatch the signed beta release workflow. The technical review is complete and green, but enabling that trust model needs explicit owner approval. No merge was made.

@frahlg
frahlg merged commit 3113b3c into srcfl:main Aug 7, 2026
5 checks passed
@frahlg

frahlg commented Aug 7, 2026

Copy link
Copy Markdown
Member

Squash-merged as 3113b3c after explicit owner approval. The post-merge Check passed, including lint, repository checks, and amd64/aarch64 build, restart, and persistence smoke tests: https://github.com/srcfl/home-assistant-addons/actions/runs/31206045862

The Sync upstream FTW and Auto publish beta workflows are active. The merge did not trigger a sync or beta release; their pilot-evidence and exact-SHA guards remain in place.

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.

4 participants