ci: stop release tail clobbering .fork-sync-state cursor [skip ci]
Reset .fork-sync-state to v11.3.0 (last-synced upstream tag) and remove the release-tail step that overwrote it with the fork release tag, which would stall upstream-sync. Update SESSION-STATE/TODO/CICD-STATUS/README for the live signed+notarized v100.1.0 release.
This commit is contained in:
6 files changed
+50
-50
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
v100.1.0
|
||||
v11.3.0
|
||||
@@ -197,22 +197,12 @@ jobs:
|
||||
tag_name: ${{ steps.set_release_info.outputs.tag_name }}
|
||||
body: ${{ steps.set_release_info.outputs.body }}
|
||||
files: ${{ env.XCODE_BUILD_PATH }}/*.zip
|
||||
# alt-tab-free [depaywall]: advance the committed last-synced-tag state to the released tag and
|
||||
# commit it, so the upstream-sync cron syncs the OLDEST upstream tag strictly newer than this —
|
||||
# not "newest tag minus --is-ancestor". This makes resolve-in-order hold by construction and
|
||||
# prevents a closed-unmerged sync PR from silently dropping commits (PLAN §4.2). Skipped with
|
||||
# [skip ci] so it does not re-trigger the workflow.
|
||||
- name: advance .fork-sync-state to the released tag
|
||||
if: github.event_name == 'push' && steps.release-gate.outputs.signing-ready == 'true'
|
||||
run: |
|
||||
tag="${{ steps.set_release_info.outputs.tag_name }}"
|
||||
[ -n "$tag" ] || { echo "no tag_name output — skipping .fork-sync-state advance"; exit 0; }
|
||||
printf '%s\n' "$tag" > .fork-sync-state
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
if ! git diff --quiet -- .fork-sync-state; then
|
||||
git add .fork-sync-state
|
||||
git commit -m "chore(sync-state): advance to $tag [skip ci]"
|
||||
git push origin "HEAD:${{ github.ref_name }}"
|
||||
fi
|
||||
# alt-tab-free [depaywall]: the release tail intentionally does NOT advance .fork-sync-state.
|
||||
# That file is the UPSTREAM-sync cursor — the last-synced UPSTREAM tag (e.g. v11.3.0), read by
|
||||
# upstream_sync.yml to pick "the oldest upstream tag strictly newer than it." The fork versions
|
||||
# independently (semantic-release cuts v100.x), so a prior revision's step that wrote the FORK
|
||||
# release tag here set the cursor to v100.x — after which no upstream tag is ever "newer" and
|
||||
# upstream-sync would silently never fire. The cursor must instead be advanced by the
|
||||
# upstream-sync merge flow, recording the UPSTREAM tag it synced (to be wired with
|
||||
# upstream_sync.yml — PLAN §4.2). Until then .fork-sync-state stays at the last real upstream tag.
|
||||
# alt-tab-free [depaywall]: update_website.sh (lwouis website dispatch) REMOVED (PLAN §3.4/§4.2).
|
||||
@@ -18,11 +18,11 @@ license as upstream. See [`LICENCE.md`](LICENCE.md) for the full text and
|
||||
brew install --cask koftwentytwo/tap/commandtabfree
|
||||
```
|
||||
|
||||
CommandTabFree is **not yet signed or notarized**, so the cask removes the download
|
||||
quarantine to avoid a Gatekeeper prompt. You can also download the **`.dmg`** (or `.zip`)
|
||||
from the [Releases](https://github.com/KofTwentyTwo/CommandTabFree/releases) page and drag
|
||||
CommandTabFree to Applications — since it's unsigned, right-click the app and choose **Open**
|
||||
on first launch. On first launch, grant **Accessibility** and **Screen Recording** in System
|
||||
CommandTabFree is **signed and notarized** (as of v100.1.0), so it launches cleanly through
|
||||
Gatekeeper — no right-click-**Open** workaround needed. Install via the Homebrew cask above, or
|
||||
download the **`.dmg`** (or `.zip`) from the
|
||||
[Releases](https://github.com/KofTwentyTwo/CommandTabFree/releases) page and drag CommandTabFree
|
||||
to Applications. On first launch, grant **Accessibility** and **Screen Recording** in System
|
||||
Settings. Universal binary (Apple Silicon + Intel), macOS 10.13+.
|
||||
|
||||
## Based on AltTab by Louis Pontoise
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
# CI/CD readiness — CommandTabFree (fork of lwouis/alt-tab-macos)
|
||||
|
||||
**Date:** 2026-06-16 **Branch:** `master` **Repo:** `KofTwentyTwo/CommandTabFree` (public)
|
||||
**Status:** push CI verified **fully green** on the real runner (run `27634828800`, commit `6070f0cb`).
|
||||
**Status:** full release pipeline **proven** on the real runner — signed + notarized **v100.1.0** released (run `27643432063`, Developer ID `James Maes (2X834TJ5MA)`, `spctl`-accepted + stapled). Non-release pushes stay green via the ad-hoc build path (run `27634828800`).
|
||||
**Authoritative design:** `docs/PLAN-maintained-fork.md` §4; owner checklist `docs/EXECUTION-STATUS.md` §3.4/§3.5.
|
||||
|
||||
This is the state after the CI-fix pass. The pass applied only **safe, owner-secret-independent**
|
||||
@@ -57,7 +57,7 @@ Environment.
|
||||
| Code-sign keychain import | `APPLE_P12_CERTIFICATE` | `setup_ci_master.sh` |
|
||||
| Notarize + staple | `APPLE_ID` + `APPLE_PASSWORD` + `APPLE_TEAM_ID` | `package_and_notarize_release.sh` |
|
||||
| Sparkle appcast sign + publish to gh-pages | `SPARKLE_ED_PRIVATE_KEY` (+ a real `DOMAIN`/feed host) | `update_appcast.sh` (now `--ed-key-file -`) |
|
||||
| Tag push + GitHub Release | ambient `GITHUB_TOKEN` (auto) — only gated alongside the above so a release isn't cut unsigned | `npx semantic-release`, `softprops/action-gh-release`, `.fork-sync-state` advance |
|
||||
| Tag push + GitHub Release | ambient `GITHUB_TOKEN` (auto) — only gated alongside the above so a release isn't cut unsigned | `npx semantic-release`, `softprops/action-gh-release` (the release-tail `.fork-sync-state` advance step was **REMOVED** 2026-06-16 — it clobbered the upstream-sync cursor with the fork release tag; the cursor must be advanced by the sync-merge flow instead — see `ci_cd.yml`) |
|
||||
| Sync PR fires `guard.yml`/`ci_cd.yml` PR checks | `SYNC_BOT_TOKEN` (GitHub App token or fork PAT — NOT default `GITHUB_TOKEN`) | `upstream_sync.yml` (fail-closed if missing) |
|
||||
|
||||
> The release tail is **all-or-nothing**: it requires the full Apple set **and** the Sparkle key
|
||||
|
||||
+11
-10
@@ -1,18 +1,19 @@
|
||||
# SESSION-STATE — CommandTabFree (de-paywalled AltTab fork)
|
||||
|
||||
_Updated 2026-06-16. `master` carries the fork; **push CI is fully green** (gate + tests + ad-hoc build; release tail skips until owner secrets). Public pre-release **v100.0.3** live; Homebrew tap live._
|
||||
_Updated 2026-06-16. `master` carries the fork; the **full CI/CD publishing pipeline is LIVE** — signed + notarized **v100.1.0** released and Gatekeeper-accepted. Homebrew tap live._
|
||||
|
||||
## ✅ CI GREEN — remaining work is owner-gated only
|
||||
## ✅ SIGNED + NOTARIZED RELEASE LIVE
|
||||
|
||||
**Push CI is fully green** as of 2026-06-16 (run `27634828800`, commit `6070f0cb`). Every owner-secret-independent step passes — Guard A/B/C, commitlint, the genstrings generated-files gate, version compute, `run_tests.sh`, and an **ad-hoc** `build_app.sh`. The entire release tail (notarize / appcast / semantic-release / gh-release / sync-state advance) SKIPS cleanly until the owner adds the Apple/Sparkle secrets — by design (CICD-STATUS bucket A).
|
||||
The complete release pipeline ran green end-to-end (run `27643432063`, commit `2f9fac17`): cert import → **signed** `build_app.sh` (Developer ID **`James Maes (2X834TJ5MA)`**) → notarize + staple → appcast→gh-pages → semantic-release → GitHub Release. **[v100.1.0](https://github.com/KofTwentyTwo/CommandTabFree/releases/tag/v100.1.0)** is published; the downloaded asset verifies `spctl` = "accepted, source=Notarized Developer ID" and `stapler validate` passes (offline-clean, no quarantine prompt). Non-release pushes stay green via the ad-hoc `build_app.sh` path (run `27634828800`).
|
||||
|
||||
Two fixes landed this session:
|
||||
1. **`fix(l10n)` `d1e1b296`** — regenerated the base `resources/l10n/Localizable.strings` from source. It had drifted after the About redesign + brand sweep; the gate regenerates via `genstrings` and diffs (the file is a pure generated mirror — keys ARE the English source literals, so it can't be hand-branded). The diff was verified byte-for-byte against CI's own Xcode 26.0.1 genstrings output before pushing. Attribution preserved (it's in the source, not hand-edited). Dead paywall strings stay upstream-branded (merge-surface minimal).
|
||||
2. **`fix(ci)` `6070f0cb`** — the Release build was inheriting upstream's Developer ID (`Developer ID Application: Louis Pontoise (QXD7GW8FHY)`, in `config/release.xcconfig:5`) and failing on CI with exit 65 (no such cert). `build_app.sh` now forwards xcodebuild overrides (`"$@"`), and `ci_cd.yml` builds **ad-hoc** (`CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO OTHER_CODE_SIGN_FLAGS=--timestamp=none`) when the release gate is not signing-ready. Auto-switches to signed when the owner provisions the cert AND sets the fork Developer ID in `config/local.xcconfig` (currently commented, lines 45-50).
|
||||
**How signing is wired:** `config/local.xcconfig` sets `CODE_SIGN_IDENTITY` + `CODE_SIGN_STYLE = Manual` + `TEAM_ID 2X834TJ5MA`; the vendored Sparkle helpers (`vendor/Sparkle/Helpers/*`) are committed **re-signed** under the same Developer ID; `setup_ci_master.sh` imports the cert with an optional `APPLE_P12_PASSWORD`. The release secrets (`APPLE_P12_CERTIFICATE`/`APPLE_P12_PASSWORD`/`APPLE_ID`/`APPLE_PASSWORD`/`APPLE_TEAM_ID` + `SPARKLE_ED_PRIVATE_KEY`) are set in the `production` env. Getting here also required two CI fixes earlier this session: `d1e1b296` (regenerate base `Localizable.strings` to clear the genstrings gate) and `6070f0cb` (ad-hoc build until the cert existed).
|
||||
|
||||
**Owner-gated next steps (NOT automatable; details in EXECUTION-STATUS §3.3–3.5 + CICD-STATUS.md bucket C):** Apple Developer ID → `APPLE_P12_CERTIFICATE`/`APPLE_ID`/`APPLE_PASSWORD`/`APPLE_TEAM_ID` in the `production` env (unblocks signed+notarized releases) **and** set `CODE_SIGN_IDENTITY` in `config/local.xcconfig`; real `DOMAIN`/appcast host to replace `fork.invalid`; `SYNC_BOT_TOKEN` (auto upstream-sync PRs); external cron for `upstream_sync.yml`. The 3 sync labels (`sync`/`conflict`/`chokepoint-refresh`) were created this session. Already set: `SPARKLE_ED_PRIVATE_KEY` + `production` env + Sparkle public key in Info.plist.
|
||||
**Remaining (NOT yet done):**
|
||||
- **Auto-update** (deferred by choice): `appcast.xml` is published to gh-pages, but the app's feed URL is still `fork.invalid` and `SUEnableAutomaticChecks=false`. To enable N→N+1 updates: set a real `DOMAIN` (kof22.com), serve gh-pages at `https://<DOMAIN>/appcast.xml`, flip checks on.
|
||||
- **Homebrew cask**: now notarized — drop the quarantine-strip postflight and bump the cask to v100.1.0.
|
||||
- **Upstream-sync**: `.fork-sync-state` reset to `v11.3.0` (the release tail no longer clobbers it — see ci_cd.yml). Before enabling upstream-sync, advance the cursor from the sync-merge flow (recording the UPSTREAM tag), and provision `SYNC_BOT_TOKEN` + an external cron. The 3 sync labels exist.
|
||||
|
||||
**Repo facts:** GitHub = `KofTwentyTwo/CommandTabFree` (renamed from alt-tab-free; local dir is still `~/Git.Local/kof22/alt-tab-free`, SSH remote points at CommandTabFree). Latest release v100.0.3 (zip + dmg). ⌘⇥ app/menu-bar icon is FINAL (owner-approved). master tip is green (functional fixes `d1e1b296` + `6070f0cb`; docs/labels/TODO commits sit on top), working tree clean. Owner-gated checklist: `docs/TODO.md`.
|
||||
**Repo facts:** GitHub = `KofTwentyTwo/CommandTabFree` (local dir `~/Git.Local/kof22/alt-tab-free`, SSH remote). Latest release **v100.1.0** (signed + notarized zip). ⌘⇥ icon FINAL. Owner-gated checklist: `docs/TODO.md`.
|
||||
|
||||
|
||||
## What this is
|
||||
@@ -22,8 +23,8 @@ Two fixes landed this session:
|
||||
- De-paywall + anti-relock tests + frozen identity (`com.koftwentytwo.commandtabfree`) + brand sweep + ⌘⇥ icon (final) — all on **`master`** (was `depaywall-free`, fast-forward merged, HEAD `c7d2d968`, pushed to origin; source is public for GPL §6).
|
||||
- **Sparkle EdDSA keypair generated.** Public key in `Info.plist` (`SUPublicEDKey`); private key in 1Password item **"CommandTabFree-Sparkle-EdDSA"** + GitHub Actions secret `SPARKLE_ED_PRIVATE_KEY` (env `production`); keychain account `commandtabfree`. `SUEnableAutomaticChecks=false` until a real appcast host exists. This fixed the launch-time "updater failed to start" alert.
|
||||
- **Public release v100.0.3** (pre-release, **unsigned / not notarized**, universal x86_64+arm64) — ⌘⇥ menu-bar icon, redesigned About (KofTwentyTwo identity + AltTab attribution links), and a **full l10n rebrand** (product name → CommandTabFree across base/en + all ~20 translated languages; upstream attribution kept). Earlier v100.0.0/v100.0.1/v100.0.2 superseded.
|
||||
- **Homebrew tap live:** `brew install --cask koftwentytwo/tap/commandtabfree` (`KofTwentyTwo/homebrew-tap` → `Casks/commandtabfree.rb`). Cask strips the quarantine (postflight `xattr`) for the unsigned app.
|
||||
- **CI/CD wired** (GitHub Actions), **fully green on push** (run `27634828800`, commit `6070f0cb`). Passing: Guard A/B/C, commitlint, generated-files gate, version compute, `run_tests.sh`, ad-hoc `build_app.sh`. The sign/notarize/appcast/semantic-release/gh-release tail is gated on a `release-gate` step that **auto-activates** when `APPLE_*` secrets are added (so missing secrets SKIP, not fail). Fixed this session (2026-06-16): regenerated base `Localizable.strings` to clear the genstrings gate; build ad-hoc until the owner provisions the Apple cert (was inheriting upstream's Developer ID → exit 65). Earlier this session: Guard B grep now matches the shipped `guard false else { return .pro }` patch (was checking the PLAN's never-shipped `if false`/`alt-tab-free [depaywall]` form); `update_appcast.sh` → `--ed-key-file`; commitlint sync-merge `ignores`; dropped upstream-only scripts (AppCenter/website); commit headers must be ≤72 chars. Full status: `docs/CICD-STATUS.md`.
|
||||
- **Homebrew tap live:** `brew install --cask koftwentytwo/tap/commandtabfree` (`KofTwentyTwo/homebrew-tap` → `Casks/commandtabfree.rb`). Cask strips the quarantine (postflight `xattr`) — was needed for the unsigned v100.0.x; now that v100.1.0 is notarized, drop that postflight and bump the cask.
|
||||
- **CI/CD wired** (GitHub Actions), **fully green on push** (run `27634828800`, commit `6070f0cb`). Passing: Guard A/B/C, commitlint, generated-files gate, version compute, `run_tests.sh`, ad-hoc `build_app.sh`. The sign/notarize/appcast/semantic-release/gh-release tail is gated on a `release-gate` step that **auto-activates** when `APPLE_*` secrets are added — secrets were added 2026-06-16 and the **full tail is now proven** (signed+notarized **v100.1.0**, run `27643432063`). Fixed this session (2026-06-16): regenerated base `Localizable.strings` to clear the genstrings gate; build ad-hoc until the owner provisions the Apple cert (was inheriting upstream's Developer ID → exit 65). Earlier this session: Guard B grep now matches the shipped `guard false else { return .pro }` patch (was checking the PLAN's never-shipped `if false`/`alt-tab-free [depaywall]` form); `update_appcast.sh` → `--ed-key-file`; commitlint sync-merge `ignores`; dropped upstream-only scripts (AppCenter/website); commit headers must be ≤72 chars. Full status: `docs/CICD-STATUS.md`.
|
||||
- **Local daily driver installed:** `/Applications/CommandTabFree.app` = **Release** build (no QA menu) signed with the `Local Self-Signed` cert (stable signature → no TCC re-grant loop). Remaining: user grants Accessibility + Screen Recording on first launch.
|
||||
|
||||
### Build recipes (verified this session)
|
||||
|
||||
+23
-14
@@ -1,25 +1,34 @@
|
||||
# TODO — CommandTabFree
|
||||
|
||||
_Updated 2026-06-16. Push CI is fully green. Everything below is **OWNER-GATED** — no agent can do these (they need Apple credentials, DNS/hosting, or tokens). Full detail: `docs/CICD-STATUS.md` bucket C, `docs/EXECUTION-STATUS.md` §3._
|
||||
_Updated 2026-06-16. The **signed + notarized publishing pipeline is LIVE** — **v100.1.0** is released (Gatekeeper-accepted). Remaining items are auto-update, the Homebrew cask, and upstream-sync. Full detail: `docs/CICD-STATUS.md`, `docs/EXECUTION-STATUS.md` §3._
|
||||
|
||||
## Blocking the full publishing pipeline (signed/notarized releases + auto-update)
|
||||
- [ ] **Apple Developer ID** — obtain a "Developer ID Application" cert for the fork; base64 it into secret `APPLE_P12_CERTIFICATE`; re-sign vendored `vendor/Sparkle/Helpers/*` under the same ID (PLAN §3.3).
|
||||
- [ ] **Set `CODE_SIGN_IDENTITY`** in `config/local.xcconfig` (lines 45-50, currently commented) to the fork's Developer ID. CI then builds **signed** instead of ad-hoc (the `build app (signed …)` step activates).
|
||||
- [ ] **Notarization creds** — `APPLE_ID`, `APPLE_PASSWORD` (app-specific password), `APPLE_TEAM_ID` in the `production` Environment.
|
||||
- [ ] **Real `DOMAIN`/appcast host** — replace `fork.invalid` in `config/local.xcconfig`; serve `https://<DOMAIN>/appcast.xml` (gh-pages); `git rm appcast.xml` + gitignore it; flip `SUEnableAutomaticChecks` back on (PLAN §3.2).
|
||||
## ✅ Signed + notarized releases — DONE (2026-06-16)
|
||||
- [x] Apple Developer ID `Developer ID Application: James Maes (2X834TJ5MA)` provisioned; cert + app-specific password in `production` secrets (`APPLE_P12_CERTIFICATE`/`APPLE_P12_PASSWORD`/`APPLE_ID`/`APPLE_PASSWORD`/`APPLE_TEAM_ID`).
|
||||
- [x] `CODE_SIGN_IDENTITY` + Manual style + `TEAM_ID` set in `config/local.xcconfig`.
|
||||
- [x] Vendored `vendor/Sparkle/Helpers/*` committed re-signed under the fork Developer ID.
|
||||
- [x] Full release tail proven on CI (run `27643432063`): signed → notarized → stapled → released. **v100.1.0** published.
|
||||
|
||||
> When all four land, the release tail (`release-gate` → notarize → appcast → semantic-release → gh-release) flips on automatically — no further code change.
|
||||
## Auto-update (deferred by choice — appcast already publishes to gh-pages)
|
||||
- [ ] **Real `DOMAIN`** — replace `fork.invalid` in `config/local.xcconfig` with the fork host (kof22.com).
|
||||
- [ ] Serve the gh-pages `appcast.xml` at `https://<DOMAIN>/appcast.xml` (CNAME the gh-pages site).
|
||||
- [ ] `git rm` the in-tree `appcast.xml` skeleton + gitignore it (feed is published out-of-tree to gh-pages); flip `SUEnableAutomaticChecks` back on (PLAN §3.2).
|
||||
|
||||
## Upstream-sync automation (independent of publishing)
|
||||
- [ ] **`SYNC_BOT_TOKEN`** — GitHub App installation token (preferred) or fork PAT with contents + pull-requests write. Default `GITHUB_TOKEN` does NOT re-trigger PR checks, so guards never run on the sync PR (`upstream_sync.yml` fails closed without it).
|
||||
- [ ] **External cron** — hit `repository_dispatch` (type `upstream-sync`) from an external scheduler (immune to GitHub's 60-day in-repo `schedule:` auto-disable) (PLAN §4.2).
|
||||
- [x] Sync PR labels `sync` / `conflict` / `chokepoint-refresh` created (2026-06-16).
|
||||
## Homebrew cask (now that builds are notarized)
|
||||
- [ ] Drop the quarantine-strip postflight (`xattr`) from `KofTwentyTwo/homebrew-tap` `Casks/commandtabfree.rb` — no longer needed for a notarized app.
|
||||
- [ ] Bump the cask to v100.1.0.
|
||||
|
||||
## Upstream-sync automation
|
||||
- [x] `.fork-sync-state` reset to `v11.3.0`; release tail no longer clobbers the cursor with the fork tag (`ci_cd.yml`).
|
||||
- [ ] **Re-wire the cursor advance** into the upstream-sync merge flow so it records the synced **UPSTREAM** tag (not the fork release tag) — `upstream_sync.yml` (PLAN §4.2).
|
||||
- [ ] **`SYNC_BOT_TOKEN`** — GitHub App installation token (or fork PAT with contents + pull-requests write). Default `GITHUB_TOKEN` does NOT re-trigger PR checks, so guards never run on the sync PR.
|
||||
- [ ] **External cron** — hit `repository_dispatch` (type `upstream-sync`) from an external scheduler (immune to GitHub's 60-day in-repo `schedule:` auto-disable).
|
||||
- [x] Sync PR labels `sync` / `conflict` / `chokepoint-refresh` created.
|
||||
|
||||
## Bootstrap acceptance tests (after the above)
|
||||
- [ ] Sync PR through the bot shows guard checks running.
|
||||
- [ ] A deliberately-relocked branch goes RED on `guard.yml`.
|
||||
- [ ] Fork build N→N+1 auto-updates via Sparkle.
|
||||
- [ ] Fork build N→N+1 auto-updates via Sparkle (needs DOMAIN/appcast live).
|
||||
|
||||
## Done this session (2026-06-16)
|
||||
## Done earlier this session (2026-06-16)
|
||||
- [x] genstrings consistency gate fixed — regenerated base `Localizable.strings` from source (`d1e1b296`).
|
||||
- [x] `build_app.sh` builds ad-hoc until cert provisioned; was failing on upstream's Developer ID (`6070f0cb`). Push CI fully green (run `27634828800`).
|
||||
- [x] `build_app.sh` builds ad-hoc until cert provisioned; was failing on upstream's Developer ID (`6070f0cb`). Push CI green (run `27634828800`).
|
||||
Reference in new issue
Block a user