From b53709df96825697872d8210051b21f5f6f94e40 Mon Sep 17 00:00:00 2001 From: Naoyuki Sogo Date: Fri, 18 Sep 2026 17:59:57 +0900 Subject: [PATCH 1/2] docs: document the dev/YYYY -> maint/YYYY branch strategy Branch naming and roles were never written down, which led to inconsistent conventions across long-lived branches (e.g. maintenance2023 and maintenance2026 predate this and were named for the calendar year they were forked, not the fiscal-year dev cycle they continue). Going forward: - dev/YYYY is the active development branch for fiscal year YYYY. - once its cycle is done, it is merged into main and renamed in place to maint/YYYY (not re-created), becoming hotfix-only. - from that rename until the next dev/YYYY+1 exists, hotfixes target the current maint/YYYY, not main directly. - both dev/YYYY and maint/YYYY are protected: PR-only, no direct commits. - main only moves forward at a release cut (dev/YYYY merge, or a maint/YYYY hotfix line merged forward for a patch release). - maintenance2023/maintenance2026 are grandfathered as-is. Also add a one-line pointer in AGENTS.md so this is followed strictly. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu --- AGENTS.md | 1 + README.md | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index d3794076..ac4ca607 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,6 +21,7 @@ test/cypress/component component test with cypress - never commit, revert or make any other operations to git repo without explicitly order from user. - do not hand-edit "server/app/db/version.json". It holds a fixed development placeholder in the repo; the real value is baked in only at build time (Dockerfile / build_and_deploy.yml), never by a running CI job. See documentMD/design/design.md ("バージョン番号の管理"). - Do not use conditional skip in unit tests except for pre-existing ones. +- Follow the branch strategy documented in README.md's "Branch strategy" section strictly (which branch to target for feature work vs. hotfixes, naming, no direct commits to `dev/YYYY`/`maint/YYYY`). ## implementation policy - always write code in async/await style diff --git a/README.md b/README.md index 389c55b6..4e5b5a05 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,14 @@ Any other markdown files under documentMD is detailed informatin for developpers "client" and "server" has client and server code respectively. "test" contains E2E test code based on cypress, you can find server-side unit test code under server/test +### Branch strategy +- `main` reflects the latest published release. It only moves forward when a release is cut - either by merging in `dev/YYYY` at a cycle-end release, or by merging a `maint/YYYY` hotfix line forward for a patch release. Do not open feature/bugfix pull requests directly against `main` - see below for where changes should target instead. +- `dev/YYYY` is the active development branch for fiscal year YYYY, branched off `main`. All feature and bugfix pull requests during an active development cycle target this branch. +- Once `dev/YYYY`'s cycle is done, it is merged into `main` (a release point) and then renamed in place (not re-created) to `maint/YYYY`, becoming a hotfix-only branch that receives no new features. +- From that rename until the next `dev/YYYY+1` is created, any hotfix targets the current `maint/YYYY`, not `main` directly. +- Both `dev/YYYY` and `maint/YYYY` are protected: no direct commits/pushes, changes only land via reviewed pull requests. +- `maintenance2023` and `maintenance2026` predate this naming rule and are kept as-is (legacy exceptions); any new long-lived release-maintenance branch going forward uses the `maint/YYYY` scheme. + ### preparation run following commands 1. npm install From 9d446688d34585e0e8758a4851556a44ca25344a Mon Sep 17 00:00:00 2001 From: Naoyuki Sogo Date: Sat, 19 Sep 2026 03:08:47 +0900 Subject: [PATCH 2/2] ci: align build_and_deploy.yml with the new dev/YYYY -> maint/YYYY branch strategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Only trigger on merged PRs targeting main, maint/**, or dev/** - previously pull_request_target had no branches filter at all, so every merged PR to every branch in the repo (including short-lived fix/*/docs/* branches) produced a full ~1.4GB release (wheel.sif + wheel_docker.tar.gz + source.tar.gz). This is the root cause of the recent release-artifact pile-up (24 GitHub Releases, most from routine PR merges rather than deliberate release points). - Add SAFE_BRANCH_NAME (BRANCH_NAME with "/" stripped) and use it for the Docker tag and source tarball's top-level directory name - dev/YYYY and maint/YYYY contain a slash, which a Docker tag cannot. - Classify dev/* as beta (previously fell through to the generic "else" case) and maint/* as maintenance (dropping the old flat maintenance* compat pattern - maintenance2023/maintenance2026 run their own independently-committed copy of this workflow file via pull_request_target, so this change has no effect on them either way). - Give each release type a distinct, immediately visible title prefix (no native GitHub Release mechanism distinguishes more than prerelease/latest/draft, so this is convention-based): - release (main): "✅ Release ..." - maintenance (maint/YYYY): "🔧 Maintenance ..." - beta (dev/YYYY): "🧪 Beta ..." - maintenance releases are real (non-prerelease) releases - they are genuine backport fixes, not experimental builds - but never take the "Latest" badge/redirect; only main does. Only beta is marked prerelease. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01C3jKNM1qubomM8UTRdkEWu --- .github/workflows/build_and_deploy.yml | 47 ++++++++++++++++++-------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 0f79db63..23ff3e0b 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -4,6 +4,10 @@ on: pull_request_target: types: - closed + branches: + - main + - "maint/**" + - "dev/**" jobs: build_and_deploy: if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true @@ -16,42 +20,54 @@ jobs: steps: - uses: actions/checkout@v4 - uses: eWaterCycle/setup-apptainer@v2 - - name: determine version number for this build + - name: determine version number and release type for this build # This value is baked into the image via the WHEEL_VERSION build-arg (see # the docker build step) and into source.tar.gz below. It is never # committed back: main is branch-protected, and the per-branch # `[skip ci] update version number` commits used to conflict on # server/app/db/version.json between parallel PRs (issue #1014). + # + # SAFE_BRANCH_NAME strips "/" from BRANCH_NAME - dev/YYYY and maint/YYYY + # contain a slash, which Docker tags (and, less critically, the source + # tarball's top-level directory name) cannot contain. run: | case "${BRANCH_NAME}" in main) TYPE=release ;; - maintenance*) TYPE=maintenance ;; + maint/*) TYPE=maintenance ;; + dev/*) TYPE=beta ;; *) TYPE=beta ;; esac + case "${TYPE}" in + release) TITLE_PREFIX="✅ Release" ;; + maintenance) TITLE_PREFIX="🔧 Maintenance" ;; + beta) TITLE_PREFIX="🧪 Beta" ;; + esac + echo "SAFE_BRANCH_NAME=$(echo "${BRANCH_NAME}" | tr '/' '-')" >>$GITHUB_ENV echo "VERSION_NUMBER=$(date '+%Y-%m%d-%H%M%S')-${TYPE}" >>$GITHUB_ENV # only main builds (TYPE=release) own the `latest` docker tag and the # GitHub "Latest" release; maintenance*/beta builds must not. echo "RELEASE_TYPE=${TYPE}" >>$GITHUB_ENV - - run: docker build --build-arg WHEEL_VERSION="${VERSION_NUMBER}" -t ${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:${BRANCH_NAME} . + echo "TITLE_PREFIX=${TITLE_PREFIX}" >>$GITHUB_ENV + - run: docker build --build-arg WHEEL_VERSION="${VERSION_NUMBER}" -t ${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:${SAFE_BRANCH_NAME} . - run: docker login -u ${DOCKER_USER_NAME} -p ${DOCKER_PASSWORD} - - run: docker push ${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:${BRANCH_NAME} + - run: docker push ${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:${SAFE_BRANCH_NAME} - name: publish :latest for mainline releases only if: env.RELEASE_TYPE == 'release' run: | - docker tag ${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:${BRANCH_NAME} \ + docker tag ${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:${SAFE_BRANCH_NAME} \ ${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:latest docker push ${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:latest - - run: docker save ${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:${BRANCH_NAME} | gzip > wheel_docker.tar.gz - - run: apptainer build wheel.sif docker-daemon://${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:${BRANCH_NAME} + - run: docker save ${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:${SAFE_BRANCH_NAME} | gzip > wheel_docker.tar.gz + - run: apptainer build wheel.sif docker-daemon://${DOCKER_USER_NAME}/${DOCKER_CONTAINER_NAME}:${SAFE_BRANCH_NAME} - name: pack source.tar.gz with the version baked in # git archive would ship the committed placeholder version.json; extract # the tracked tree, drop in the real version, and re-pack so a run from # source.tar.gz reports the same version as the images (issue #1014). run: | rm -rf _src && mkdir -p _src - git archive --prefix=OpenWHEEL-${BRANCH_NAME}/ HEAD | tar -x -C _src - echo "{\"version\": \"${VERSION_NUMBER}\" }" > _src/OpenWHEEL-${BRANCH_NAME}/server/app/db/version.json - tar -czf source.tar.gz -C _src OpenWHEEL-${BRANCH_NAME} + git archive --prefix=OpenWHEEL-${SAFE_BRANCH_NAME}/ HEAD | tar -x -C _src + echo "{\"version\": \"${VERSION_NUMBER}\" }" > _src/OpenWHEEL-${SAFE_BRANCH_NAME}/server/app/db/version.json + tar -czf source.tar.gz -C _src OpenWHEEL-${SAFE_BRANCH_NAME} rm -rf _src - name: create release and upload artifact uses: softprops/action-gh-release@v2 @@ -61,13 +77,14 @@ jobs: wheel_docker.tar.gz source.tar.gz tag_name: ${{ env.VERSION_NUMBER }} - name: ${{ env.VERSION_NUMBER }} + name: "${{ env.TITLE_PREFIX }} ${{ env.VERSION_NUMBER }}" body: "release for ${{ github.ref_name }} @ ${{ github.sha }} (${{ env.VERSION_NUMBER }})" - # only the main line is the "Latest" release; maintenance*/beta ship - # as pre-releases so they never take the badge or the /releases/latest - # redirect (v1 silently ignored these inputs). + # only the main line ever takes the "Latest" badge and the + # /releases/latest redirect (v1 silently ignored these inputs). + # maintenance backports are real, non-draft releases (not "pre"), they + # just never become "Latest" - only beta (dev/YYYY) is marked prerelease. make_latest: ${{ env.RELEASE_TYPE == 'release' }} - prerelease: ${{ env.RELEASE_TYPE != 'release' }} + prerelease: ${{ env.RELEASE_TYPE == 'beta' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} backup: