Текущий процесс (README.md:154-211) описывает release-ветку от develop, ff-only merge в main и обратную синхронизацию PR main → develop. Back-merge существует только ради коммита chore(release): X.Y.Z, который создаётся на release-ветке и потому отсутствует в develop. Он оставляет merge-коммит в develop при каждом релизе.
Gitea при этом не принуждает линейность: во всех пяти репозиториях allow_merge_commits: true и default_merge_style: merge. В истории main присутствуют merge-коммиты (d2d1092, 3321d9d, ed8221a в root; 774e58e в backend).
Проверено на момент создания Issue: origin/main является предком origin/develop во всех пяти репозиториях. Переход возможен без переписывания истории.
Решение
Модель:
feature/* --squash|rebase-merge--> develop --ff-only--> main --> tag vX.Y.Z
Инвариант: origin/main всегда предок origin/develop.
Релиз:
git fetch origin --tags; ветка release/X.Y.Z от свежего develop
npm run release:preview, затем npm run release:prepare
PR release/X.Y.Z → develop, title chore(release): X.Y.Z, ff-only merge
PR develop → main, тот же title, ff-only merge
git tag -a vX.Y.Z <main-sha> -m "Release X.Y.Z"; git push origin refs/tags/vX.Y.Z
Убирается: back-merge main → develop, cherry-pick, rebase develop, release/hotfix-* от main.
Не меняется: .release-it.json (requireBranch: "release/*" работает без правок), commitlint.config.cjs, workflows.
Ограничения, принимаемые явно
Freeze релиза не поддерживается: нельзя заморозить кандидат и параллельно мержить в develop.
Hotfix — обычная ветка fix/* от develop с последующим релизом. Если develop содержит незаконченную работу, быстрого фикса нет. Escape hatch (release от main + cherry-pick) допустим как разовое исключение ценой нарушения инварианта и последующего back-merge.
Acceptance Criteria
.ai/conventions/git.md, README.md и .ai/conventions/engineering-workflow.md описывают модель выше; back-merge и hotfix от main удалены
Инвариант и команда его проверки записаны в документах
Gitea repo settings во всех пяти: allow_merge_commits: false, allow_rebase_explicit: false, default_merge_style: squash, default_update_style: rebase, default_delete_branch_after_merge: true
Порядок: документы в каждом repo независимы, Gitea-настройки применяются после merge документов.
## Контекст
Текущий процесс (README.md:154-211) описывает release-ветку от `develop`, ff-only merge в `main` и обратную синхронизацию PR `main → develop`. Back-merge существует только ради коммита `chore(release): X.Y.Z`, который создаётся на release-ветке и потому отсутствует в `develop`. Он оставляет merge-коммит в `develop` при каждом релизе.
Gitea при этом не принуждает линейность: во всех пяти репозиториях `allow_merge_commits: true` и `default_merge_style: merge`. В истории `main` присутствуют merge-коммиты (`d2d1092`, `3321d9d`, `ed8221a` в root; `774e58e` в backend).
Проверено на момент создания Issue: `origin/main` является предком `origin/develop` во всех пяти репозиториях. Переход возможен без переписывания истории.
## Решение
Модель:
```
feature/* --squash|rebase-merge--> develop --ff-only--> main --> tag vX.Y.Z
```
**Инвариант: `origin/main` всегда предок `origin/develop`.**
Релиз:
1. `git fetch origin --tags`; ветка `release/X.Y.Z` от свежего `develop`
2. `npm run release:preview`, затем `npm run release:prepare`
3. PR `release/X.Y.Z → develop`, title `chore(release): X.Y.Z`, ff-only merge
4. PR `develop → main`, тот же title, ff-only merge
5. `git tag -a vX.Y.Z <main-sha> -m "Release X.Y.Z"`; `git push origin refs/tags/vX.Y.Z`
Убирается: back-merge `main → develop`, cherry-pick, rebase `develop`, `release/hotfix-*` от `main`.
Не меняется: `.release-it.json` (`requireBranch: "release/*"` работает без правок), `commitlint.config.cjs`, workflows.
## Ограничения, принимаемые явно
- Freeze релиза не поддерживается: нельзя заморозить кандидат и параллельно мержить в `develop`.
- Hotfix — обычная ветка `fix/*` от `develop` с последующим релизом. Если `develop` содержит незаконченную работу, быстрого фикса нет. Escape hatch (release от `main` + cherry-pick) допустим как разовое исключение ценой нарушения инварианта и последующего back-merge.
## Acceptance Criteria
- [ ] `.ai/conventions/git.md`, `README.md` и `.ai/conventions/engineering-workflow.md` описывают модель выше; back-merge и hotfix от `main` удалены
- [ ] Инвариант и команда его проверки записаны в документах
- [ ] Gitea repo settings во всех пяти: `allow_merge_commits: false`, `allow_rebase_explicit: false`, `default_merge_style: squash`, `default_update_style: rebase`, `default_delete_branch_after_merge: true`
- [ ] Branch protection `main`/`develop` выровнена между пятью репозиториями: `enable_push: false`, `enable_force_push: false`, `block_on_outdated_branch: true`, корректные `status_check_contexts`
- [ ] Tag protection `v*` подтверждена во всех пяти
- [ ] Инвариант проверен после применения во всех пяти
## Связанные Issue
- WealthLedger/backend
- WealthLedger/frontend
- WealthLedger/docs
- WealthLedger/specs
Порядок: документы в каждом repo независимы, Gitea-настройки применяются после merge документов.
Инвариант origin/main предок origin/develop — выполняется во всех пяти репозиториях.
Merge-настройки идентичны во всех пяти и разрешают merge-коммиты: allow_merge_commits=true, allow_squash_merge=true, allow_fast_forward_only_merge=true, allow_rebase=true, allow_rebase_explicit=true, default_merge_style=merge, default_update_style=merge, default_delete_branch_after_merge=false.
Branch protection существует во всех пяти (rule_name: {main,develop}), но расходится:
Выполнено. Все пять PR слиты squash: main#35, backend#112, frontend#92, docs#50, specs#49.
Gitea после применения
Repo settings во всех пяти: allow_merge_commits=false, allow_rebase_explicit=false, allow_rebase=true, allow_squash_merge=true, allow_fast_forward_only_merge=true, default_merge_style=squash, default_update_style=rebase, default_delete_branch_after_merge=true.
Branch protection {main,develop} во всех пяти выровнена: enable_push=false, enable_push_whitelist=false, enable_force_push=false, enable_force_push_allowlist=false, enable_status_check=true, block_on_outdated_branch=true, block_admin_merge_override=true.
Устранённые расхождения baseline: frontend разрешал force-push и не блокировал устаревшую ветку; backend разрешал прямой push ksv741.
Tag protection v* с whitelist ksv741 подтверждена во всех пяти. Контексты обязательных проверок оставлены как были — они уже соответствовали workflows каждого repo.
Verification
Инвариант git merge-base --is-ancestor origin/main origin/develop — ok во всех пяти после merge.
origin/develop HEAD каждого repo — одиночный squash-коммит без merge-записи:
main 0bdc409
frontend eefdde2
backend 4f485ab
docs 11f819e
specs 06d08e2
Полная сверка настроек по API: пять из пяти ALL OK по 14 проверкам (merge styles, protection, tag protection).
CI на финальных ревизиях: commits в root; verify, security, commits в backend/frontend; verify, security, commits в docs/specs — все success.
Рабочие ветки удалены на сервере (delete_branch=true) и локально.
Не входило в scope
.release-it.json и commitlint.config.cjs не менялись — requireBranch: "release/*" и ignore-правило chore(release): X.Y.Z работают с новым процессом без правок. Релизный PR теперь идёт в develop, что покрывается существующей веткой-ограничением.
Первый релиз по новой схеме подтвердит, что ветка release/X.Y.Z → develop проходит ff-only merge: develop не должен двигаться, пока PR открыт.
Выполнено. Все пять PR слиты squash: main#35, backend#112, frontend#92, docs#50, specs#49.
## Gitea после применения
Repo settings во всех пяти:
`allow_merge_commits=false`, `allow_rebase_explicit=false`, `allow_rebase=true`, `allow_squash_merge=true`, `allow_fast_forward_only_merge=true`, `default_merge_style=squash`, `default_update_style=rebase`, `default_delete_branch_after_merge=true`.
Branch protection `{main,develop}` во всех пяти выровнена:
`enable_push=false`, `enable_push_whitelist=false`, `enable_force_push=false`, `enable_force_push_allowlist=false`, `enable_status_check=true`, `block_on_outdated_branch=true`, `block_admin_merge_override=true`.
Устранённые расхождения baseline: frontend разрешал force-push и не блокировал устаревшую ветку; backend разрешал прямой push `ksv741`.
Tag protection `v*` с whitelist `ksv741` подтверждена во всех пяти. Контексты обязательных проверок оставлены как были — они уже соответствовали workflows каждого repo.
## Verification
Инвариант `git merge-base --is-ancestor origin/main origin/develop` — `ok` во всех пяти после merge.
`origin/develop` HEAD каждого repo — одиночный squash-коммит без merge-записи:
- main `0bdc409`
- frontend `eefdde2`
- backend `4f485ab`
- docs `11f819e`
- specs `06d08e2`
Полная сверка настроек по API: пять из пяти `ALL OK` по 14 проверкам (merge styles, protection, tag protection).
CI на финальных ревизиях: `commits` в root; `verify`, `security`, `commits` в backend/frontend; `verify`, `security`, `commits` в docs/specs — все `success`.
Рабочие ветки удалены на сервере (`delete_branch=true`) и локально.
## Не входило в scope
`.release-it.json` и `commitlint.config.cjs` не менялись — `requireBranch: "release/*"` и ignore-правило `chore(release): X.Y.Z` работают с новым процессом без правок. Релизный PR теперь идёт в `develop`, что покрывается существующей веткой-ограничением.
Первый релиз по новой схеме подтвердит, что ветка `release/X.Y.Z → develop` проходит ff-only merge: `develop` не должен двигаться, пока PR открыт.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Контекст
Текущий процесс (README.md:154-211) описывает release-ветку от
develop, ff-only merge вmainи обратную синхронизацию PRmain → develop. Back-merge существует только ради коммитаchore(release): X.Y.Z, который создаётся на release-ветке и потому отсутствует вdevelop. Он оставляет merge-коммит вdevelopпри каждом релизе.Gitea при этом не принуждает линейность: во всех пяти репозиториях
allow_merge_commits: trueиdefault_merge_style: merge. В историиmainприсутствуют merge-коммиты (d2d1092,3321d9d,ed8221aв root;774e58eв backend).Проверено на момент создания Issue:
origin/mainявляется предкомorigin/developво всех пяти репозиториях. Переход возможен без переписывания истории.Решение
Модель:
Инвариант:
origin/mainвсегда предокorigin/develop.Релиз:
git fetch origin --tags; веткаrelease/X.Y.Zот свежегоdevelopnpm run release:preview, затемnpm run release:preparerelease/X.Y.Z → develop, titlechore(release): X.Y.Z, ff-only mergedevelop → main, тот же title, ff-only mergegit tag -a vX.Y.Z <main-sha> -m "Release X.Y.Z";git push origin refs/tags/vX.Y.ZУбирается: back-merge
main → develop, cherry-pick, rebasedevelop,release/hotfix-*отmain.Не меняется:
.release-it.json(requireBranch: "release/*"работает без правок),commitlint.config.cjs, workflows.Ограничения, принимаемые явно
develop.fix/*отdevelopс последующим релизом. Еслиdevelopсодержит незаконченную работу, быстрого фикса нет. Escape hatch (release отmain+ cherry-pick) допустим как разовое исключение ценой нарушения инварианта и последующего back-merge.Acceptance Criteria
.ai/conventions/git.md,README.mdи.ai/conventions/engineering-workflow.mdописывают модель выше; back-merge и hotfix отmainудаленыallow_merge_commits: false,allow_rebase_explicit: false,default_merge_style: squash,default_update_style: rebase,default_delete_branch_after_merge: truemain/developвыровнена между пятью репозиториями:enable_push: false,enable_force_push: false,block_on_outdated_branch: true, корректныеstatus_check_contextsv*подтверждена во всех пятиСвязанные Issue
Порядок: документы в каждом repo независимы, Gitea-настройки применяются после merge документов.
Связанные Issue созданы:
Baseline, снятый перед работой:
Инвариант
origin/mainпредокorigin/develop— выполняется во всех пяти репозиториях.Merge-настройки идентичны во всех пяти и разрешают merge-коммиты:
allow_merge_commits=true,allow_squash_merge=true,allow_fast_forward_only_merge=true,allow_rebase=true,allow_rebase_explicit=true,default_merge_style=merge,default_update_style=merge,default_delete_branch_after_merge=false.Branch protection существует во всех пяти (
rule_name: {main,develop}), но расходится:Conventional commits / commits (pull_request)Frontend verify / {commits,security,verify} (pull_request)Backend verify / {commits,security,verify} (pull_request)Docs verify / verify,Docs security / security,Conventional commits / commitsSpecs verify / verify,Specs security / security,Conventional commits / commitsTag protection
v*с whitelistksv741присутствует во всех пяти. Контексты уже корректны везде, дополнительный проход за ними не нужен.Выравнивание frontend (force_push, outdated) и backend (прямой push) входит в scope этой Issue.
Выполнено. Все пять PR слиты squash: main#35, backend#112, frontend#92, docs#50, specs#49.
Gitea после применения
Repo settings во всех пяти:
allow_merge_commits=false,allow_rebase_explicit=false,allow_rebase=true,allow_squash_merge=true,allow_fast_forward_only_merge=true,default_merge_style=squash,default_update_style=rebase,default_delete_branch_after_merge=true.Branch protection
{main,develop}во всех пяти выровнена:enable_push=false,enable_push_whitelist=false,enable_force_push=false,enable_force_push_allowlist=false,enable_status_check=true,block_on_outdated_branch=true,block_admin_merge_override=true.Устранённые расхождения baseline: frontend разрешал force-push и не блокировал устаревшую ветку; backend разрешал прямой push
ksv741.Tag protection
v*с whitelistksv741подтверждена во всех пяти. Контексты обязательных проверок оставлены как были — они уже соответствовали workflows каждого repo.Verification
Инвариант
git merge-base --is-ancestor origin/main origin/develop—okво всех пяти после merge.origin/developHEAD каждого repo — одиночный squash-коммит без merge-записи:0bdc409eefdde24f485ab11f819e06d08e2Полная сверка настроек по API: пять из пяти
ALL OKпо 14 проверкам (merge styles, protection, tag protection).CI на финальных ревизиях:
commitsв root;verify,security,commitsв backend/frontend;verify,security,commitsв docs/specs — всеsuccess.Рабочие ветки удалены на сервере (
delete_branch=true) и локально.Не входило в scope
.release-it.jsonиcommitlint.config.cjsне менялись —requireBranch: "release/*"и ignore-правилоchore(release): X.Y.Zработают с новым процессом без правок. Релизный PR теперь идёт вdevelop, что покрывается существующей веткой-ограничением.Первый релиз по новой схеме подтвердит, что ветка
release/X.Y.Z → developпроходит ff-only merge:developне должен двигаться, пока PR открыт.