Merge feature/30 into integration/spec16 (ticket #30: upstream intake policy)
This commit is contained in:
@@ -367,6 +367,37 @@ bash -n install.sh
|
||||
./install.sh --help | grep -- --tag
|
||||
```
|
||||
|
||||
### Upstream Fix Intake (Cherry-Pick Policy)
|
||||
|
||||
Upstream Vocalinux fixes enter this fork only as hand cherry-picks: taken at
|
||||
defined sync points, never as continuous tracking and never as wholesale
|
||||
merges. Like every other change, each port goes through the pull request flow
|
||||
with the standard test, lint, and type gates. Never land one as a direct or
|
||||
bot commit to `main`, and follow the worktree rules in `AGENTS.md` (one
|
||||
worktree per branch, every change via a pull request, never commit on `main`
|
||||
— only the pull request base changes).
|
||||
|
||||
- **When intake happens.** At each release-preparation cycle, while working
|
||||
through the steps above on the release-prep branch, plus on demand for
|
||||
critical fixes: crashes, data loss, and security issues. Critical fixes
|
||||
follow the Emergency Hotfix Process below.
|
||||
- **When intake freezes.** Intake freezes once the release branch opens
|
||||
(Step 6). After the freeze, only justified release-blockers are admitted;
|
||||
everything else waits for the next cycle.
|
||||
- **One upstream fix per pull request.** Each port is its own pull request,
|
||||
referencing both the upstream fix and the fork issue/PR numbers.
|
||||
- **How conflicts resolve.** Keep the upstream logic and re-apply the fork's
|
||||
renames on top. Conflicts concentrate at the renamed user-visible surfaces
|
||||
(application name, entry points, desktop and D-Bus identity, configuration
|
||||
and data directories, package, website) precisely because the internal
|
||||
Python module name stays `vocalinux` as a retained internal name — that
|
||||
split is what keeps hand cherry-picks nearly conflict-free everywhere else.
|
||||
Upstream URLs and changelog history stay untouched for provenance.
|
||||
- **What qualifies.** Fixes and documentation that changes user instructions
|
||||
port. Features are re-decided per release, not ported automatically.
|
||||
Dependency and CI churn ports only when a security advisory names it.
|
||||
Out-of-scope material is dropped at triage.
|
||||
|
||||
### Step 6: Create Release Branch and PR
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
"""The release documentation carries the upstream-fix intake policy.
|
||||
|
||||
Ticket #30 (spec #16): the fork's upstream-intake rule must live where
|
||||
maintainers run releases from, not only on a decision ticket. A maintainer
|
||||
following only docs/RELEASE_PROCESS.md must be able to state when intake
|
||||
happens, when it freezes, what qualifies, and how conflicts resolve.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
RELEASE_PROCESS = REPO_ROOT / "docs" / "RELEASE_PROCESS.md"
|
||||
|
||||
|
||||
def _policy() -> str:
|
||||
assert RELEASE_PROCESS.is_file(), "docs/RELEASE_PROCESS.md is missing"
|
||||
return RELEASE_PROCESS.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_policy_states_when_intake_happens() -> None:
|
||||
policy = _policy()
|
||||
assert "release-preparation" in policy or "release preparation" in policy
|
||||
assert "on demand" in policy or "on-demand" in policy
|
||||
|
||||
|
||||
def test_policy_states_freeze_once_release_branch_opens() -> None:
|
||||
policy = _policy()
|
||||
assert "freeze" in policy.lower()
|
||||
assert "release branch" in policy.lower()
|
||||
assert "release-blocker" in policy or "release blocker" in policy
|
||||
|
||||
|
||||
def test_policy_states_one_fix_per_pull_request_through_standard_gates() -> None:
|
||||
policy = _policy()
|
||||
assert "one upstream fix per" in policy.lower() or "one upstream fix" in policy.lower()
|
||||
assert "pull request" in policy.lower()
|
||||
|
||||
|
||||
def test_policy_states_conflict_resolution() -> None:
|
||||
policy = _policy()
|
||||
assert "keep" in policy.lower() and "upstream" in policy.lower()
|
||||
assert "re-apply" in policy.lower() or "reapply" in policy.lower()
|
||||
|
||||
|
||||
def test_policy_states_qualification_rule() -> None:
|
||||
policy = _policy()
|
||||
lowered = policy.lower()
|
||||
assert "security advisory" in lowered # dependency/CI churn ports only then
|
||||
assert "re-decided" in lowered # features are re-decided per release
|
||||
assert "triage" in lowered # out-of-scope material is dropped at triage
|
||||
|
||||
|
||||
def test_policy_names_retained_internal_module_name() -> None:
|
||||
policy = _policy()
|
||||
assert "vocalinux" in policy
|
||||
assert "internal" in policy.lower()
|
||||
|
||||
|
||||
def test_policy_keeps_pull_request_flow_and_never_commit_to_main() -> None:
|
||||
policy = _policy().lower()
|
||||
assert "pull request" in policy
|
||||
assert "never" in policy and "main" in policy
|
||||
Reference in New Issue
Block a user