feat: synchronize native metadata on promotion PRs - #73
Conversation
📝 WalkthroughWalkthroughPromotion Sync now handles promotion pull requests separately from implementation pull requests. It aggregates metadata, updates Project v2 state and backlinks, and adds unit and live QA coverage. ChangesPromotion synchronization
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Sync
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@project_setup/promotion_sync.py`:
- Around line 148-158: The sync_promotion_native_metadata assignee logic only
adds missing assignees and never removes stale ones, including when
metadata.assignees is empty. Reconcile the issue’s current assignees to the
aggregate metadata.assignees union by removing assignees absent from the union
and adding missing ones, while preserving dry-run behavior and any established
manual-assignee policy. Update tests/test_promotion_sync.py lines 95-164 to
cover stale-assignee removal and an empty-assignee aggregate.
In `@tests/qa/live_promotion_sync.py`:
- Around line 549-556: Extend the promotion sync validation after the promotion
comment check to iterate through every PR in related_prs, fetch its comments via
client.list_issue_comments, and require a constituent backlink comment whose
stage marker references promotion_number and whose state is merged. Raise a
RuntimeError identifying the related PR when the expected backlink is missing or
incorrect, while preserving the existing promotion-comment assertion.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: d599fd06-5bd0-4ab5-b0eb-5c5bb6362b50
📒 Files selected for processing (9)
.github/workflows/qa-live.ymldocs/repo/pr-governance-architecture.mddocs/repo/pr-governance-architecture.pt-BR.mddocs/repo/pr-sync.mddocs/repo/pr-sync.pt-BR.mdproject_setup/pr_sync_router.pyproject_setup/promotion_sync.pytests/qa/live_promotion_sync.pytests/test_promotion_sync.py
| if config.get("syncAssignees", True): | ||
| current_assignees = set(issue_assignee_logins(pr_issue)) | ||
| missing = [login for login in metadata.assignees if login not in current_assignees] | ||
| if missing: | ||
| if dry_run: | ||
| print(f"[DRY-RUN] Would assign promotion PR #{ctx.number} to {', '.join(missing)}") | ||
| else: | ||
| add_assignees(client, repo, ctx.number, missing) | ||
| notes.append( | ||
| "assignees=" + (", ".join(f"`{login}`" for login in metadata.assignees) if metadata.assignees else "none") | ||
| ) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect available assignee mutation helpers before adding reconciliation.
ast-grep outline project_setup/pr_sync.py --items all
rg -n -C 4 'add_assignees|update_issue|/assignees|assignees' project_setup tests/test_promotion_sync.pyRepository: v-Kaefer/Github-Project-Automation
Length of output: 21456
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- promotion_sync.py focused source ---'
cat -n project_setup/promotion_sync.py | sed -n '1,175p'
printf '%s\n' '--- related tests ---'
cat -n tests/test_promotion_sync.py | sed -n '1,190p'
printf '%s\n' '--- assignee configuration/docs ---'
rg -n -C 5 'syncAssignees|assignee|manual.assignee|assignAuthorWhenTaskUnassigned' \
--glob '!*.pyc' --glob '!node_modules' .
printf '%s\n' '--- issue-assignee API client methods ---'
rg -n -C 5 'def (request_json|update_issue)|issues/.*/assignees|assignees' project_setupRepository: v-Kaefer/Github-Project-Automation
Length of output: 50392
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
cases = [
("stale", {"alice", "stale"}, ["alice"]),
("aggregate_empty", {"stale"}, []),
("already_converged", {"alice", "bob"}, ["alice", "bob"]),
]
for name, current, desired in cases:
missing = [login for login in desired if login not in current]
print(name, {"current": sorted(current), "desired": desired, "missing": missing,
"result_after_current_logic": sorted(current | set(missing)),
"stale_remaining": sorted(current - set(desired))})
PY
printf '%s\n' '--- documented promotion assignee contract ---'
sed -n '54,92p;199,211p' docs/repo/pr-sync.mdRepository: v-Kaefer/Github-Project-Automation
Length of output: 2710
🌐 Web query:
GitHub REST API update an issue assignees array remove assignee endpoint
💡 Result:
To remove assignees from a GitHub issue, you have two primary options using the GitHub REST API depending on whether you want to remove specific users or clear all assignees entirely. 1. Removing specific assignees: You can remove one or more specific users from an issue by sending a DELETE request to the following endpoint [1][2]: DELETE /repos/{owner}/{repo}/issues/{issue_number}/assignees In the request body, provide an array of strings containing the usernames of the assignees you wish to remove [3][2]: { "assignees": ["username1", "username2"] } 2. Updating (replacing or clearing) assignees: You can also manage assignees using the Update an issue endpoint, which uses a PATCH request [4][5]: PATCH /repos/{owner}/{repo}/issues/{issue_number} When using the "assignees" parameter in the body, the provided list replaces the entire set of current assignees [4][6]. To remove all assignees from an issue, send an empty array ([]) [4][7]: { "assignees": [] } Please note that only users with push access to the repository can modify assignees [4][2]. Without appropriate permissions, these changes are typically silently ignored or dropped [1][4].
Citations:
- 1: https://docs.github.com/en/rest/issues/assignees?apiVersion=2026-03-10
- 2: https://docs.github.com/rest/issues/assignees?apiVersion=2022-11-28
- 3: https://docs.github.com/en/rest/issues/assignees
- 4: https://docs.github.com/en/rest/issues/issues?apiVersion=2026-03-10
- 5: https://docs.github.com/rest/issues/issues
- 6: https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28
- 7: https://docs.github.com/en/rest/issues/issues
Reconcile promotion assignees to the aggregate union.
sync_promotion_native_metadata leaves stale assignees when a Related PR removes an assignee or when the aggregate union becomes empty.
- Reconcile assignees with the aggregate union, or define a manual-assignee policy.
- Add tests for stale and empty-assignee cases.
📍 Affects 2 files
project_setup/promotion_sync.py#L148-L158(this comment)tests/test_promotion_sync.py#L95-L164
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@project_setup/promotion_sync.py` around lines 148 - 158, The
sync_promotion_native_metadata assignee logic only adds missing assignees and
never removes stale ones, including when metadata.assignees is empty. Reconcile
the issue’s current assignees to the aggregate metadata.assignees union by
removing assignees absent from the union and adding missing ones, while
preserving dry-run behavior and any established manual-assignee policy. Update
tests/test_promotion_sync.py lines 95-164 to cover stale-assignee removal and an
empty-assignee aggregate.
| comments = client.list_issue_comments(args.repo, promotion_number) | ||
| promotion_comment = next( | ||
| (comment for comment in comments if "<!-- project-setup-related-prs -->" in (comment.get("body") or "")), | ||
| None, | ||
| ) | ||
| if not promotion_comment or "State: `merged`" not in (promotion_comment.get("body") or ""): | ||
| raise RuntimeError("Promotion Sync sticky comment did not converge to merged state") | ||
| print("promotion_sync_backlinks=passed") |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Validate backlinks on each constituent PR.
This code reads comments only from the promotion PR. It does not verify the backlinks written to the PRs in related_prs. A broken constituent-backlink loop can pass this test.
Fetch each related PR comment list. Assert that its stage marker links to this promotion and reports the merged state.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/qa/live_promotion_sync.py` around lines 549 - 556, Extend the promotion
sync validation after the promotion comment check to iterate through every PR in
related_prs, fetch its comments via client.list_issue_comments, and require a
constituent backlink comment whose stage marker references promotion_number and
whose state is merged. Raise a RuntimeError identifying the related PR when the
expected backlink is missing or incorrect, while preserving the existing
promotion-comment assertion.
Promotion linkage
|
Promotion linkage
|
Linked Issue
Milestone
Related PRs
Summary
How to test
Evidence
promotion_sync.py,linked_branch.py,pr_project_sync.py, andproject_lookup.pyKnown risks
PROJECT_SETUP_PAT;PROJECT_SETUP_PROJECT_NUMBERis now optional when one exact Project title matchesprojectDefinitionFile.name.DoD checklist