Merge feature/30 into integration/spec16 (ticket #30: upstream intake policy)

This commit is contained in:
soubarnak
2026-11-06 00:06:02 +05:30
2 changed files with 93 additions and 0 deletions
+31
View File
@@ -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
+62
View File
@@ -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