Some checks failed
Go Benchmark / Performance Regression Check (push) Has been cancelled
Cerberus Integration / Cerberus Security Stack Integration (push) Has been cancelled
Upload Coverage to Codecov / Backend Codecov Upload (push) Has been cancelled
Upload Coverage to Codecov / Frontend Codecov Upload (push) Has been cancelled
CodeQL - Analyze / CodeQL analysis (go) (push) Has been cancelled
CodeQL - Analyze / CodeQL analysis (javascript-typescript) (push) Has been cancelled
CrowdSec Integration / CrowdSec Bouncer Integration (push) Has been cancelled
Docker Build, Publish & Test / build-and-push (push) Has been cancelled
Quality Checks / Auth Route Protection Contract (push) Has been cancelled
Quality Checks / Codecov Trigger/Comment Parity Guard (push) Has been cancelled
Quality Checks / Backend (Go) (push) Has been cancelled
Quality Checks / Frontend (React) (push) Has been cancelled
Rate Limit integration / Rate Limiting Integration (push) Has been cancelled
Security Scan (PR) / Trivy Binary Scan (push) Has been cancelled
Supply Chain Verification (PR) / Verify Supply Chain (push) Has been cancelled
WAF integration / Coraza WAF Integration (push) Has been cancelled
Docker Build, Publish & Test / Security Scan PR Image (push) Has been cancelled
Repo Health Check / Repo health (push) Has been cancelled
History Rewrite Dry-Run / Dry-run preview for history rewrite (push) Has been cancelled
Prune Renovate Branches / prune (push) Has been cancelled
Renovate / renovate (push) Has been cancelled
Nightly Build & Package / sync-development-to-nightly (push) Has been cancelled
Nightly Build & Package / Trigger Nightly Validation Workflows (push) Has been cancelled
Nightly Build & Package / build-and-push-nightly (push) Has been cancelled
Nightly Build & Package / test-nightly-image (push) Has been cancelled
Nightly Build & Package / verify-nightly-supply-chain (push) Has been cancelled
118 lines
5.2 KiB
Markdown
Executable File
118 lines
5.2 KiB
Markdown
Executable File
# Plan: Refine Propagation Workflow to Enforce Strict Hierarchy (Pittsburgh Model)
|
|
|
|
## 1. Introduction
|
|
This plan outlines the update of the `.github/workflows/propagate-changes.yml` workflow. The goal is to enforce a strict hierarchical propagation strategy ("The Pittsburgh Model") where changes flow downstream from `main` to `development`, and then from `development` to leaf branches (`feature/*`, `hotfix/*`). This explicitly prevents "loop-backs" and direct updates from `main` to feature branches.
|
|
|
|
## 2. Methodology & Rules
|
|
**The Pittsburgh Model (Strict Hierarchy):**
|
|
|
|
1. **Rule 1 (The Ohio River)**: `main` **ONLY** propagates to `development`.
|
|
- *Logic*: `main` is the stable release branch. Changes here (hotfixes, releases) must flow into `development` first.
|
|
- *Constraint*: `main` must **NEVER** propagate directly to `feature/*` or `hotfix/*`.
|
|
|
|
2. **Rule 2 (The Point)**: `development` is the **ONLY** branch that propagates to leaf branches.
|
|
- *Logic*: `development` is the source of truth for active work. It aggregates `main` changes plus ongoing development.
|
|
- *Targets*: `feature/*` and `hotfix/*`.
|
|
|
|
3. **Rule 3 (Loop Prevention)**: Determine the "source" PR to prevent re-propagation.
|
|
- *Problem*: When `feature/A` merges into `development`, we must not open a PR from `development` back to `feature/A`.
|
|
- *Mechanism*: Identify the source branch of the commit triggering the workflow and exclude it from targets.
|
|
|
|
## 3. Workflow Design
|
|
|
|
### 3.1. Branching Strategy Logic
|
|
|
|
| Trigger Branch | Source | Target(s) | Logic |
|
|
| :--- | :--- | :--- | :--- |
|
|
| `main` | `main` | `development` | Create PR `main` -> `development` |
|
|
| `development` | `development` | `feature/*`, `hotfix/*` | Create PR `development` -> `[leaf]` (Excluding changes source) |
|
|
| `feature/*` | - | - | No action (Triggers CI only) |
|
|
| `hotfix/*` | - | - | No action (Triggers CI only) |
|
|
|
|
### 3.2. Logic Updates Needed
|
|
|
|
**A. Strict Main Enforcement**
|
|
- Current logic likely does this, but we will explicitly verify `if (currentBranch === 'main') { propagate('development'); }` and nothing else.
|
|
|
|
**B. Development Distribution & Hotfix Inclusion**
|
|
- Update the branch listing logic to find both `feature/*` AND `hotfix/*` branches.
|
|
- Current code only looks for `feature/*`.
|
|
|
|
**C. Loop Prevention (The "Source Branch" Check)**
|
|
- **Trigger**: Script runs on push to `development`.
|
|
- **Action**:
|
|
1. Retrieve the Pull Request associated with the commit sha using the GitHub API.
|
|
2. If a merged PR exists for this commit, extract the source branch name (`head.ref`).
|
|
3. Exclude this source branch from the list of propagation targets.
|
|
|
|
### 3.3. Technical Implementation Details
|
|
- **File**: `.github/workflows/propagate-changes.yml`
|
|
- **Action**: `actions/github-script`
|
|
|
|
**Pseudo-Code Update:**
|
|
```javascript
|
|
// 1. Get current branch
|
|
const branch = context.ref.replace('refs/heads/', '');
|
|
|
|
// 2. Rule 1: Main -> Development
|
|
if (branch === 'main') {
|
|
await createPR('main', 'development');
|
|
return;
|
|
}
|
|
|
|
// 3. Rule 2: Development -> Leafs
|
|
if (branch === 'development') {
|
|
// 3a. Identify Source (Rule 3 Loop Prevention)
|
|
// NOTE: This runs on push, so context.sha is the commit sha.
|
|
let excludedBranch = null;
|
|
try {
|
|
const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
commit_sha: context.sha,
|
|
});
|
|
// Find the PR that was merged
|
|
const mergedPr = prs.data.find(pr => pr.merged_at);
|
|
if (mergedPr) {
|
|
excludedBranch = mergedPr.head.ref;
|
|
core.info(`Commit derived from merged PR #${mergedPr.number} (Source: ${excludedBranch}). Skipping back-propagation.`);
|
|
}
|
|
} catch (e) {
|
|
core.info('Could not check associated PRs: ' + e.message);
|
|
}
|
|
|
|
// 3b. Find Targets
|
|
const branches = await github.paginate(github.rest.repos.listBranches, {
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
});
|
|
|
|
const targets = branches
|
|
.map(b => b.name)
|
|
.filter(b => (b.startsWith('feature/') || b.startsWith('hotfix/')))
|
|
.filter(b => b !== excludedBranch); // Exclude source
|
|
|
|
// 3c. Propagate
|
|
core.info(`Propagating to ${targets.length} branches: ${targets.join(', ')}`);
|
|
for (const target of targets) {
|
|
await createPR('development', target);
|
|
}
|
|
}
|
|
```
|
|
|
|
## 4. Implementation Steps
|
|
|
|
1. **Refactor `main` logic**: Ensure it returns immediately after propagating to `development` to prevent any fall-through.
|
|
2. **Update `development` logic**:
|
|
- Add `hotfix/` to the filter regex.
|
|
- Implement the `listPullRequestsAssociatedWithCommit` call to identify the exclusion.
|
|
- Apply the exclusion to the target list.
|
|
3. **Verify Hierarchy**:
|
|
- Confirm no path exists for `main` -> `feature/*`.
|
|
|
|
## 5. Acceptance Criteria
|
|
- [ ] Push to `main` creates a PR ONLY to `development`.
|
|
- [ ] Push to `development` creates PRs to all downstream `feature/*` AND `hotfix/*` branches.
|
|
- [ ] Push to `development` (caused by merge of `feature/A`) does **NOT** create a PR back to `feature/A`.
|
|
- [ ] A hotfix merged to `main` flows: `main` -> `development`, then `development` -> `hotfix/active-work` (if any exist).
|