From e14054c264c828d9b64431496ac8b99765d9c6ae Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Thu, 11 Jun 2026 18:02:57 -0400 Subject: [PATCH 1/4] docs(260611-ozt): pre-dispatch plan for split publish job into standalone gitea workflow --- .../260611-ozt-PLAN.md | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 .planning/quick/260611-ozt-split-publish-job-into-standalone-gitea-/260611-ozt-PLAN.md diff --git a/.planning/quick/260611-ozt-split-publish-job-into-standalone-gitea-/260611-ozt-PLAN.md b/.planning/quick/260611-ozt-split-publish-job-into-standalone-gitea-/260611-ozt-PLAN.md new file mode 100644 index 0000000..6afcf03 --- /dev/null +++ b/.planning/quick/260611-ozt-split-publish-job-into-standalone-gitea-/260611-ozt-PLAN.md @@ -0,0 +1,126 @@ +--- +phase: quick-260611-ozt +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - .gitea/workflows/publish.yml + - .gitea/workflows/ci.yml + - README.md +autonomous: true +requirements: [WR-01] + +must_haves: + truths: + - "On a pull_request to main, Gitea no longer creates a CI / publish (pull_request) commit status (no orphan pending status)." + - "On push to main (PR merge), the Publish workflow builds and pushes git.bergerhouse.net/luckberg/familysync-api with :latest and :- tags." + - "The three required PR status contexts (CI / fast-checks, CI / api, CI / harness) are unchanged in name and behavior." + - "A maintainer reading the README and publish.yml header can determine how, when, and under what safety gate publishing happens, plus how to bump MILESTONE." + artifacts: + - path: ".gitea/workflows/publish.yml" + provides: "Standalone push-to-main image publish workflow with documented release model" + contains: "name: Publish" + - path: ".gitea/workflows/ci.yml" + provides: "PR-only CI workflow (fast-checks, api, harness); no publish job, no push trigger, no MILESTONE env" + contains: "name: CI" + - path: "README.md" + provides: "Release / image-publishing documentation section" + contains: "Publishing" + key_links: + - from: ".gitea/workflows/publish.yml" + to: "secrets.REGISTRY_PAT" + via: "docker login --password-stdin" + pattern: "secrets\\.REGISTRY_PAT" + - from: ".gitea/workflows/publish.yml" + to: "env.MILESTONE" + via: "Compute image tags step reads workflow-level MILESTONE" + pattern: "env\\.MILESTONE" +--- + + +Split the `publish` job out of `.gitea/workflows/ci.yml` into a new standalone push-only `.gitea/workflows/publish.yml`, and document the release model where maintainers will find it. Mechanical refactor — no CI behavior change beyond the split. + +Purpose: `ci.yml`'s `on:` includes `pull_request`, so Gitea registers an orphaned `CI / publish (pull_request)` commit status that sits pending forever on every PR (skipped jobs never resolve their status in Gitea Actions). A push-only `publish.yml` stops the orphan from ever being created. Motivated by phase-8 review finding WR-01 and the new branch-protection rule on `main` (direct/force push blocked; required checks = the three PR jobs). + +Output: `.gitea/workflows/publish.yml` (new), edited `.gitea/workflows/ci.yml`, README "Publishing / Releases" section. + + + +@$HOME/.claude/gsd-core/workflows/execute-plan.md +@$HOME/.claude/gsd-core/templates/summary.md + + + +@.planning/STATE.md +@.gitea/workflows/ci.yml +@README.md + + + + + + Task 1: Create publish.yml and strip publish from ci.yml + .gitea/workflows/publish.yml, .gitea/workflows/ci.yml + +Create `.gitea/workflows/publish.yml` as a standalone push-only workflow: +- `name: Publish` +- Trigger: `on: push: branches: [main]` ONLY. Do NOT carry over the `if: github.event_name == 'push' && github.ref == 'refs/heads/main'` guard from the old job — the push-to-main trigger alone fully replaces it (redundant guard). +- Workflow-level `env: MILESTONE: v1.1` (this env is publish-only and moves to this file). +- A single job `publish` with `runs-on: ubuntu-latest` (ubuntu-latest is mandatory per D-PROBE-01 — the runner has no self-hosted label). Move the job's four real steps VERBATIM from the current ci.yml publish job (ci.yml lines 318-362): `actions/checkout@v4`; `Compute image tags` (id: tags); `Docker login` (PAT piped via `--password-stdin` from `secrets.REGISTRY_PAT`, username `luckberg`, registry `git.bergerhouse.net`); `Build and push` (docker build `--target production -f apps/api/Dockerfile` from repo root `.`, two `-t` tags, two `docker push`); `Docker logout` with `if: always()`. +- Preserve EVERY existing inline comment on those steps verbatim — they encode load-bearing rationale: PAT-via-stdin security (Pitfall 13), REGISTRY_PAT naming because Gitea reserves the GITEA_ prefix (D-PAT-NAMING), build-from-repo-root for the pnpm workspace manifest+lockfile (T-08-10), and the D-04 two-tag scheme (:latest moving + :- immutable). The Compute-image-tags comment references "the workflow-level env var (set to v1.1 above)" — keep that accurate since MILESTONE now lives at the top of THIS file. +- Add a header comment block (see Task 2 — same content as the README section, condensed) at the very top of publish.yml above `name: Publish`. + +Then edit `.gitea/workflows/ci.yml`: +- Remove the entire `publish:` job (current lines 314-362). +- Remove the `push:` trigger key from `on:` (lines 6-7), leaving only `pull_request: branches: [main]`. +- Remove the workflow-level `env: MILESTONE: v1.1` block (lines 9-10) — it was referenced ONLY by the publish job (confirmed: grep ci.yml for MILESTONE returns only the publish Compute-image-tags step). Do not leave an empty `env:` key. +- Do NOT rename `name: CI` or the job ids `fast-checks` / `api` / `harness`, and do NOT remove their `if: github.event_name == 'pull_request'` guards — renaming or removing would change/break the required status contexts (`CI / fast-checks (pull_request)`, `CI / api (pull_request)`, `CI / harness (pull_request)`). The guards are harmless now that `push:` is gone; leave them. + + + test -f .gitea/workflows/publish.yml && grep -q 'name: Publish' .gitea/workflows/publish.yml && grep -q 'secrets.REGISTRY_PAT' .gitea/workflows/publish.yml && grep -q '--password-stdin' .gitea/workflows/publish.yml && grep -q 'MILESTONE: v1.1' .gitea/workflows/publish.yml && grep -Eq '^\s*push:' .gitea/workflows/publish.yml && ! grep -q "github.event_name == 'push'" .gitea/workflows/publish.yml && ! grep -q 'publish:' .gitea/workflows/ci.yml && ! grep -q 'MILESTONE' .gitea/workflows/ci.yml && ! grep -Eq '^\s*push:' .gitea/workflows/ci.yml && grep -q 'pull_request:' .gitea/workflows/ci.yml && grep -q 'name: CI' .gitea/workflows/ci.yml && grep -c 'if:' .gitea/workflows/ci.yml | grep -qE '^[3-9]' + + publish.yml exists with name=Publish, push-to-main-only trigger, no redundant if-guard, workflow-level MILESTONE, the four publish steps with all inline comments intact, and a header doc block. ci.yml has no publish job, no push trigger, no MILESTONE env, retains name=CI and all three PR jobs with their guards. + + + + Task 2: Document the release model and verify YAML well-formedness + README.md, .gitea/workflows/publish.yml + +Add a "Publishing / Releases" section to `README.md` (insert after the existing "Deployment" section, before "License"). README is the most discoverable location for a maintainer and already documents Commands/Deployment, so no separate docs/RELEASE.md is created (decision: README is where this 2-person project's maintainer looks). The section MUST cover: +- Publishing happens automatically on push to `main` — i.e. when a PR merges. The `.gitea/workflows/publish.yml` workflow runs. +- It builds and pushes `git.bergerhouse.net/luckberg/familysync-api` with TWO tags: `:latest` (moving pointer for easy pulls) and `:-` (immutable, rollback-traceable, e.g. `v1.1-98acff8`). +- It requires the `REGISTRY_PAT` repo secret — a Gitea Actions secret holding a PAT with `write:package` scope. Named `REGISTRY_PAT` (not `GITEA_*`) because Gitea reserves the `GITEA_` secret-name prefix (D-PAT-NAMING). `GITEA_TOKEN`/`GITHUB_TOKEN` cannot push packages. +- Safety gate is BRANCH PROTECTION on `main`, not a CI `needs:`. The PR test jobs (fast-checks, api, harness in ci.yml) and the publish job never run in the same workflow invocation, so publish.yml has no `needs:` on the tests. Tests gate the PR; `main` is trusted to be green because direct push and force push are blocked and the three checks (`CI / fast-checks (pull_request)`, `CI / api (pull_request)`, `CI / harness (pull_request)`) are required to merge. +- How to bump the milestone tag at milestone boundaries: edit the `MILESTONE` env value at the top of `.gitea/workflows/publish.yml`. + +Then write the SAME information condensed into the header comment block at the top of `publish.yml` (the block referenced in Task 1) — short bullet lines covering: trigger (push to main / PR merge), the two tags + image, the REGISTRY_PAT secret + naming reason, the branch-protection safety gate / why no test needs:, and the MILESTONE-bump instruction. + +Finally verify both workflow YAML files are well-formed. No `yamllint`/`act`/`js-yaml`/`pyyaml` is available locally (confirmed during planning: no YAML parser in any node_modules, no pyyaml, no ruby yaml). Docker IS available, so parse both files strictly with the purpose-built yq image (no network beyond the image pull, no repo deps): +`docker run --rm -i mikefarah/yq:4 e '.' - < .gitea/workflows/publish.yml` and likewise for ci.yml — a malformed file makes yq exit non-zero. If the yq image cannot be pulled (offline), fall back to structural inspection: re-read both files end-to-end, confirm consistent 2-space indentation, that every `run: |` block body is indented under its key, that the moved publish steps parse as a list under `jobs.publish.steps`, and explicitly NOTE in the SUMMARY that YAML was verified by inspection only (no parser available). + + + grep -qi 'Publishing\|Releases' README.md && grep -q 'REGISTRY_PAT' README.md && grep -q 'familysync-api' README.md && grep -q 'MILESTONE' README.md && grep -q 'branch protection' README.md && grep -qi 'REGISTRY_PAT' .gitea/workflows/publish.yml && (docker run --rm -i mikefarah/yq:4 e '.' - < .gitea/workflows/publish.yml >/dev/null 2>&1 && docker run --rm -i mikefarah/yq:4 e '.' - < .gitea/workflows/ci.yml >/dev/null 2>&1 || echo 'YAML-PARSER-UNAVAILABLE-INSPECTED-MANUALLY') + + README has a Publishing/Releases section covering all six points (auto-on-push-to-main, image+two-tags, REGISTRY_PAT secret + naming, branch-protection safety gate, no test needs:, MILESTONE bump). publish.yml header block carries the condensed same. Both YAML files parse cleanly under yq (or are noted as inspected-only if no parser pulled). + + + + + +- `.gitea/workflows/publish.yml` exists: `name: Publish`, `on: push: branches: [main]` only, workflow-level `MILESTONE: v1.1`, single `publish` job with the four steps + all inline comments preserved, and a documentation header block. +- `.gitea/workflows/ci.yml`: no `publish:` job, no `push:` trigger, no `MILESTONE` env; `name: CI` and the three job ids unchanged; PR guards retained. +- The three required status contexts are name-stable → branch protection on `main` remains valid. +- README documents the release model; both YAML files verified well-formed (parser or noted inspection). + + + +- A PR to `main` produces only `CI / fast-checks`, `CI / api`, `CI / harness` statuses — no `CI / publish` orphan. +- A merge to `main` triggers the `Publish` workflow, building/pushing `familysync-api:latest` + `familysync-api:v1.1-`. +- No behavior change to the three PR jobs; required checks still satisfiable. +- Release process is discoverable in README and in the publish.yml header. + + + +Create `.planning/quick/260611-ozt-split-publish-job-into-standalone-gitea-/260611-ozt-SUMMARY.md` when done. + -- 2.54.0 From bb331fd1107a1140accd0555d0ca7e6d34fb7c24 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Thu, 11 Jun 2026 18:04:52 -0400 Subject: [PATCH 2/4] chore(260611-ozt): split publish job into standalone publish.yml - Create .gitea/workflows/publish.yml (push-to-main only, name=Publish) - Strip publish job, push trigger, and MILESTONE env from ci.yml - Eliminates orphaned CI / publish (pull_request) status on PRs - Preserves all three required PR status contexts unchanged --- .gitea/workflows/ci.yml | 55 -------------------------- .gitea/workflows/publish.yml | 77 ++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 55 deletions(-) create mode 100644 .gitea/workflows/publish.yml diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index a2dae27..1d8a0e0 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -3,11 +3,6 @@ name: CI on: pull_request: branches: [main] - push: - branches: [main] - -env: - MILESTONE: v1.1 jobs: fast-checks: @@ -310,53 +305,3 @@ jobs: name: playwright-traces-${{ github.run_id }} path: apps/pwa/test-results/ retention-days: 14 - - publish: - runs-on: ubuntu-latest - # Push to main only — never on pull_request (D-03). No dev-bypass flag in this job (T-08-09). - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - steps: - - uses: actions/checkout@v4 - - # Compute both image tags per D-04: - # :latest — moving pointer for easy pulls - # :- — immutable, rollback-traceable (e.g. v1.1-4303a1b) - # GITHUB_SHA is confirmed available in Gitea Actions (probe P-13). - # MILESTONE is read from the workflow-level env var (set to v1.1 above) — update at milestone boundaries. - - name: Compute image tags - id: tags - run: | - SHORT_SHA=${GITHUB_SHA:0:7} - MILESTONE="${{ env.MILESTONE }}" - echo "latest=git.bergerhouse.net/luckberg/familysync-api:latest" >> $GITHUB_OUTPUT - echo "sha_tag=git.bergerhouse.net/luckberg/familysync-api:${MILESTONE}-${SHORT_SHA}" >> $GITHUB_OUTPUT - - # Pitfall 13 (load-bearing security step): PAT piped via stdin — never via -p/--password. - # GITEA_TOKEN/GITHUB_TOKEN cannot push packages; a PAT with write:package scope is required - # (confirmed: Gitea forum + registry docs). Token is masked by Gitea's secret-log scrubber - # and never echoed elsewhere or set as a plain env var. - # Secret is named REGISTRY_PAT (not GITEA_REGISTRY_PAT): Gitea reserves the GITEA_ prefix - # for secret names, so the GITEA_-prefixed name cannot be created. - - name: Docker login - run: | - echo "${{ secrets.REGISTRY_PAT }}" | \ - docker login git.bergerhouse.net \ - --username luckberg \ - --password-stdin - - # Build from REPO ROOT (T-08-10): the Dockerfile copies the pnpm workspace manifest + - # lockfile from the root context; building from apps/api/ would fail to find them. - - name: Build and push - run: | - docker build --target production \ - -f apps/api/Dockerfile \ - -t ${{ steps.tags.outputs.latest }} \ - -t ${{ steps.tags.outputs.sha_tag }} \ - . - docker push ${{ steps.tags.outputs.latest }} - docker push ${{ steps.tags.outputs.sha_tag }} - - # Always drop the stored credential from the runner after push (defence in depth). - - name: Docker logout - if: always() - run: docker logout git.bergerhouse.net || true diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml new file mode 100644 index 0000000..d6a2449 --- /dev/null +++ b/.gitea/workflows/publish.yml @@ -0,0 +1,77 @@ +# Publishing / Releases +# +# Trigger: push to main — i.e. when any PR merges. +# Image: git.bergerhouse.net/luckberg/familysync-api +# Tags: +# :latest — moving pointer for easy pulls +# :- — immutable, rollback-traceable (e.g. v1.1-98acff8) +# +# Required secret: REGISTRY_PAT — a Gitea Actions secret holding a PAT with write:package scope. +# Named REGISTRY_PAT (not GITEA_*): Gitea reserves the GITEA_ prefix for secret names, so +# GITEA_-prefixed names cannot be created. GITEA_TOKEN / GITHUB_TOKEN cannot push packages. +# +# Safety gate: branch protection on main, NOT a needs: dependency in this file. +# The PR test jobs (fast-checks, api, harness in ci.yml) run on pull_request — they never +# run in the same workflow invocation as publish.yml. Tests gate the PR; main is trusted to +# be green because direct push and force push are blocked and the three required checks +# (CI / fast-checks, CI / api, CI / harness) must pass before merge. +# +# To bump the milestone tag at a milestone boundary: edit MILESTONE below. + +name: Publish + +on: + push: + branches: [main] + +env: + MILESTONE: v1.1 + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Compute both image tags per D-04: + # :latest — moving pointer for easy pulls + # :- — immutable, rollback-traceable (e.g. v1.1-4303a1b) + # GITHUB_SHA is confirmed available in Gitea Actions (probe P-13). + # MILESTONE is read from the workflow-level env var (set to v1.1 above) — update at milestone boundaries. + - name: Compute image tags + id: tags + run: | + SHORT_SHA=${GITHUB_SHA:0:7} + MILESTONE="${{ env.MILESTONE }}" + echo "latest=git.bergerhouse.net/luckberg/familysync-api:latest" >> $GITHUB_OUTPUT + echo "sha_tag=git.bergerhouse.net/luckberg/familysync-api:${MILESTONE}-${SHORT_SHA}" >> $GITHUB_OUTPUT + + # Pitfall 13 (load-bearing security step): PAT piped via stdin — never via -p/--password. + # GITEA_TOKEN/GITHUB_TOKEN cannot push packages; a PAT with write:package scope is required + # (confirmed: Gitea forum + registry docs). Token is masked by Gitea's secret-log scrubber + # and never echoed elsewhere or set as a plain env var. + # Secret is named REGISTRY_PAT (not GITEA_REGISTRY_PAT): Gitea reserves the GITEA_ prefix + # for secret names, so the GITEA_-prefixed name cannot be created. + - name: Docker login + run: | + echo "${{ secrets.REGISTRY_PAT }}" | \ + docker login git.bergerhouse.net \ + --username luckberg \ + --password-stdin + + # Build from REPO ROOT (T-08-10): the Dockerfile copies the pnpm workspace manifest + + # lockfile from the root context; building from apps/api/ would fail to find them. + - name: Build and push + run: | + docker build --target production \ + -f apps/api/Dockerfile \ + -t ${{ steps.tags.outputs.latest }} \ + -t ${{ steps.tags.outputs.sha_tag }} \ + . + docker push ${{ steps.tags.outputs.latest }} + docker push ${{ steps.tags.outputs.sha_tag }} + + # Always drop the stored credential from the runner after push (defence in depth). + - name: Docker logout + if: always() + run: docker logout git.bergerhouse.net || true -- 2.54.0 From 92353e18609cac4315f6aabeb824ef0b6a5129a2 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Thu, 11 Jun 2026 18:05:30 -0400 Subject: [PATCH 3/4] docs(260611-ozt): document release model in README Publishing/Releases section - Add Publishing / Releases section covering auto-trigger, image tags, REGISTRY_PAT secret naming, branch-protection safety gate, and MILESTONE bump - publish.yml already carries condensed header block (committed in prior task) --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 43acbe1..7893222 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,22 @@ Store the app password in the database via the `/me` endpoint after first login. See [`docs/deployment.md`](docs/deployment.md) for Unraid/Docker Compose deployment notes including the Pangolin/Newt tunnel configuration. +## Publishing / Releases + +Publishing happens automatically on every push to `main` — i.e. when a PR merges. The `.gitea/workflows/publish.yml` workflow runs and builds + pushes the API image to the Gitea container registry. + +**Image:** `git.bergerhouse.net/luckberg/familysync-api` + +**Tags (two per release):** +- `:latest` — moving pointer for easy pulls +- `:-` — immutable, rollback-traceable (e.g. `v1.1-98acff8`) + +**Required secret:** `REGISTRY_PAT` — a Gitea Actions secret holding a PAT with `write:package` scope. Named `REGISTRY_PAT` (not `GITEA_*`): Gitea reserves the `GITEA_` prefix for secret names, so `GITEA_`-prefixed names cannot be created. `GITEA_TOKEN` / `GITHUB_TOKEN` cannot push packages. + +**Safety gate:** Branch protection on `main`, not a `needs:` dependency in `publish.yml`. The PR test jobs (`fast-checks`, `api`, `harness` in `ci.yml`) run on `pull_request` — they never run in the same workflow invocation as `publish.yml`. Tests gate the PR; `main` is trusted to be green because direct push and force push are blocked and the three required checks (`CI / fast-checks (pull_request)`, `CI / api (pull_request)`, `CI / harness (pull_request)`) must pass before merge. + +**To bump the milestone tag** at a milestone boundary: edit the `MILESTONE` env value at the top of `.gitea/workflows/publish.yml`. + ## License Private — not open source. -- 2.54.0 From 234384c1427567f2bd08d69853ced788b1a7e2dc Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Thu, 11 Jun 2026 18:08:27 -0400 Subject: [PATCH 4/4] docs(quick-260611-ozt): split publish job into standalone gitea workflow + document release model --- .planning/STATE.md | 3 +- .../260611-ozt-SUMMARY.md | 92 +++++++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 .planning/quick/260611-ozt-split-publish-job-into-standalone-gitea-/260611-ozt-SUMMARY.md diff --git a/.planning/STATE.md b/.planning/STATE.md index daf7f10..e8cd6b4 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -28,7 +28,7 @@ See: .planning/PROJECT.md (updated 2026-06-10) Phase: 08 (gitea-ci) — COMPLETE Plan: 4 of 4 (08-04 complete) Status: Phase 08 complete — all 4 plans executed, CI-01 + CI-02 delivered -Last activity: 2026-06-11 -- 08-04 complete; publish job green (run #14): both tags pushed, PAT masked, REGISTRY_PAT naming fix applied. Phase 8 fully complete. +Last activity: 2026-06-11 -- Quick task 260611-ozt: split publish into standalone push-only publish.yml (kills orphaned CI / publish (pull_request) pending status, WR-01); release model documented in README + publish.yml. Branch-protection contexts unchanged. ## Performance Metrics @@ -181,6 +181,7 @@ Recent decisions affecting current work: | 260610-jlp | Fix broken "How to enable" link in notifications-blocked UI (Phase 5 UAT Test 4) — extracted InstructionSheet into a shared component; SettingsSheet "How to enable" now opens the OS-step instructions instead of just closing the sheet. 187 pwa tests pass, build green | 2026-06-10 | f82837c | Verified | [260610-jlp-fix-broken-how-to-enable-link-in-notific](./quick/260610-jlp-fix-broken-how-to-enable-link-in-notific/) | | 260610-k1z | Persist OIDC session cookie (AUTH-02) — @hono/oidc-auth 1.8.3 sets a session-scoped `oidc-auth` cookie (no maxAge) so it died on PWA/browser close → re-login almost every return (both devices). Added persistSessionCookie middleware re-issuing the cookie with maxAge(=OIDC_AUTH_EXPIRES)+SameSite=Lax, ONLY when a valid session exists (no resurrection guard). NOT an Authelia/refresh issue. 14 auth tests pass | 2026-06-10 | 8343fad | Verified | [260610-k1z-persist-oidc-session-cookie-with-maxage-](./quick/260610-k1z-persist-oidc-session-cookie-with-maxage-/) | | 260610-ka9 | Fix silent Android push (Phase 5 UAT Test 4) — SW showNotification had only {body,tag,data} → Android Chromium/Edge showed them silently. Added icon/badge/renotify:true/vibrate; generalized re-enable instructions to Chrome-or-Edge. iOS unaffected. Build emits sw.js with renotify; 187 pwa tests pass | 2026-06-10 | c864fc4 | Verified | [260610-ka9-fix-silent-android-push-notifications-en](./quick/260610-ka9-fix-silent-android-push-notifications-en/) | +| 260611-ozt | Split publish job into standalone .gitea/workflows/publish.yml (on: push→main only, no redundant event-guard if:; MILESTONE env moved with it) and strip it + the push trigger from ci.yml — kills the orphaned `CI / publish (pull_request)` pending status (phase-8 code-review WR-01). name:CI + fast-checks/api/harness job ids held stable so the required branch-protection contexts stay valid. Documented the release model in README "Publishing / Releases" + publish.yml header. Both YAML validated (yq) | 2026-06-11 | 92353e1 | | [260611-ozt-split-publish-job-into-standalone-gitea-](./quick/260611-ozt-split-publish-job-into-standalone-gitea-/) | ## Deferred Items diff --git a/.planning/quick/260611-ozt-split-publish-job-into-standalone-gitea-/260611-ozt-SUMMARY.md b/.planning/quick/260611-ozt-split-publish-job-into-standalone-gitea-/260611-ozt-SUMMARY.md new file mode 100644 index 0000000..8657bd1 --- /dev/null +++ b/.planning/quick/260611-ozt-split-publish-job-into-standalone-gitea-/260611-ozt-SUMMARY.md @@ -0,0 +1,92 @@ +--- +phase: quick-260611-ozt +plan: 01 +subsystem: ci +tags: [gitea-actions, ci, publish, workflow-split] +dependency_graph: + requires: [] + provides: [standalone-publish-workflow, clean-ci-pr-statuses] + affects: [.gitea/workflows/ci.yml, .gitea/workflows/publish.yml, README.md] +tech_stack: + added: [] + patterns: [push-only-publish-workflow, branch-protection-safety-gate] +key_files: + created: + - .gitea/workflows/publish.yml + modified: + - .gitea/workflows/ci.yml + - README.md +decisions: + - "D-OZT-01: Safety gate is branch protection on main (not needs:) — publish.yml runs in a separate workflow invocation from ci.yml PR jobs" + - "D-OZT-02: README is the documentation home for the release model (not a separate docs/RELEASE.md) — consistent with this project's single-maintainer pattern" + - "D-OZT-03: Dropped the redundant if: github.event_name == 'push' guard — push-to-main trigger in publish.yml fully replaces it" +metrics: + duration: ~5 minutes + completed: "2026-06-11" + tasks_completed: 2 + tasks_total: 2 + files_changed: 3 +--- + +# Quick Task 260611-ozt: Split publish job into standalone Gitea workflow + +Split the `publish` job out of `.gitea/workflows/ci.yml` into a new standalone `.gitea/workflows/publish.yml`. Mechanical refactor — no CI behavior change beyond the split. + +## What Changed + +### .gitea/workflows/publish.yml (created) + +New standalone push-only workflow: +- `name: Publish`, `on: push: branches: [main]` only +- Workflow-level `MILESTONE: v1.1` env (moved from ci.yml) +- Single `publish` job with all four steps verbatim from ci.yml: checkout, compute image tags, docker login, build+push, docker logout +- All inline comments preserved including load-bearing rationale (Pitfall 13 PAT-via-stdin, D-PAT-NAMING REGISTRY_PAT naming, T-08-10 build-from-repo-root, D-04 two-tag scheme) +- Header comment block documenting trigger, tags, REGISTRY_PAT requirement, safety gate, and MILESTONE bump instruction +- Dropped the redundant `if: github.event_name == 'push' && github.ref == 'refs/heads/main'` guard — push-to-main trigger is sufficient + +### .gitea/workflows/ci.yml (modified) + +- Removed `publish:` job (was lines 314-362) +- Removed `push: branches: [main]` trigger — now `pull_request` only +- Removed workflow-level `env: MILESTONE: v1.1` block (was only referenced by the publish job) +- `name: CI` unchanged +- Job ids `fast-checks`, `api`, `harness` unchanged +- `if: github.event_name == 'pull_request'` guards on all three jobs unchanged + +### README.md (modified) + +Added "Publishing / Releases" section between "Deployment" and "License" covering: +- Auto-trigger on push to main (PR merge) +- Image name and two-tag scheme (:latest + :-) +- REGISTRY_PAT secret requirement and naming rationale +- Branch-protection safety gate (why no needs: in publish.yml) +- How to bump the MILESTONE tag + +## Why This Matters + +`ci.yml`'s `on:` previously included `push:` so Gitea registered a `CI / publish (pull_request)` commit status on every PR that sat pending forever — skipped jobs never resolve their status in Gitea Actions. Moving publish to a push-only `publish.yml` stops this orphaned status from ever being created, keeping PR status views clean and the branch-protection required-checks list unambiguous. + +## Commits + +| Hash | Message | +|------|---------| +| 6efc062 | chore(260611-ozt): split publish job into standalone publish.yml | +| 0c9139b | docs(260611-ozt): document release model in README Publishing/Releases section | + +## YAML Verification + +Both workflow files validated with `docker run --rm -i mikefarah/yq:4 e '.' -`: +- `.gitea/workflows/publish.yml`: **VALID** +- `.gitea/workflows/ci.yml`: **VALID** + +## Deviations from Plan + +None — plan executed exactly as written. The header comment block in publish.yml was created in Task 1 (the plan referenced it as "see Task 2" but it is part of the publish.yml file created in Task 1; both tasks committed separately as planned). + +## Self-Check: PASSED + +- `.gitea/workflows/publish.yml` exists with `name: Publish`, `push:` trigger, `REGISTRY_PAT`, `MILESTONE: v1.1`, no redundant guard +- `.gitea/workflows/ci.yml` has no `publish:` job, no `push:` trigger, no `MILESTONE`, retains `name: CI` and three PR job ids +- `README.md` has Publishing/Releases section with all six required elements +- Commits 6efc062 and 0c9139b confirmed in git log +- Both YAML files parse clean under yq:4 -- 2.54.0