Skip to content

fix(sync): enforce canary promotion boundary - #3039

Merged
stranske merged 2 commits into
mainfrom
agent/sync-promotion-boundary
Aug 11, 2026
Merged

fix(sync): enforce canary promotion boundary#3039
stranske merged 2 commits into
mainfrom
agent/sync-promotion-boundary

Conversation

@stranske

@stranske stranske commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • make Maint 68 canary selection fail closed so explicit repo filters can only narrow the configured canary set
  • reuse stable sync/workflows-candidate PRs while a candidate plan is being repaired
  • reserve immutable hash-named sync branches and non-canary fan-out for plan-bound promotion
  • make drift remediation read-only until promotion and document the Maint 71 active_sync_hash=candidate handoff

Why

Candidate corrections were being dispatched directly across the fleet through the canary repo filter, producing a replacement PR wave after every source adjustment. This change turns the existing canary/evidence/promote design into an enforced boundary: repair one stable canary candidate, collect exact-plan evidence, then fan out once.

Validation

  • python -m pytest -q tests/scripts/test_select_consumer_sync_phase.py tests/scripts/test_check_consumer_sync_drift.py tests/workflows/test_sync_manifest_delivery.py (51 passed)
  • node --test .github/scripts/__tests__/sync_pr_merge_contract.test.js (23 passed)
  • ruff check on changed Python and tests
  • actionlint .github/workflows/maint-68-sync-consumer-repos.yml
  • python scripts/validate_template_sync.py
  • python scripts/validate_template_completeness.py
  • bash scripts/sync_templates.sh --check
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Added staged consumer-repository synchronization with canary-only previews and plan-bound promotion.
    • Canary corrections now use a stable candidate branch, while standard updates use uniquely identified branches.
    • Added support for narrowing manual canary runs to selected configured repositories.
  • Bug Fixes

    • Prevented non-canary repositories from being included in canary runs.
    • Improved existing pull request selection and candidate plan rotation.
  • Documentation

    • Updated workflow and maintenance guidance for staged promotion, candidate branches, and preview-based manual syncs.

@cursor

cursor Bot commented Aug 11, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds stable candidate-branch handling for configured canaries. It restricts canary selections, updates drift remediation, routes workflow PR operations through the computed branch, validates candidate refreshes, and updates tests and documentation.

Changes

Consumer sync delivery

Layer / File(s) Summary
Canary selection and drift reporting
scripts/check_consumer_sync_drift.py, scripts/select_consumer_sync_phase.py, tests/scripts/*
Canary configuration is validated. Non-canary selections are rejected. Canary repositories use sync/workflows-candidate; other repositories use hash-based branches. Remediation follow-ups use phase=preview.
Sync branch workflow and PR lifecycle
.github/workflows/maint-68-sync-consumer-repos.yml, .github/scripts/__tests__/sync_pr_merge_contract.test.js, tests/workflows/test_sync_manifest_delivery.py
The workflow computes and exports the sync branch. PR lookup, creation, refresh checks, and result artifacts use that branch. Candidate refreshes allow valid repository-owned nonterminal delivery records to rotate plans.
Operational workflow documentation
docs/WORKFLOW_GUIDE.md, docs/ci/WORKFLOWS.md, docs/ci/WORKFLOW_SYSTEM.md, docs/ops/CONSUMER_REPO_MAINTENANCE.md
Documentation describes canary-only staging, stable candidate branches, preview runs, and exact-plan promotion requirements.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WorkflowPrepare
  participant PhaseSelection
  participant ConsumerSyncPR
  participant ResultArtifact
  WorkflowPrepare->>PhaseSelection: phase and compiled template hash
  PhaseSelection->>ConsumerSyncPR: selected sync branch
  ConsumerSyncPR->>ResultArtifact: expected_branch
Loading

Possibly related PRs

Suggested labels: acceptance-criteria

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: enforcing the canary promotion boundary for consumer synchronization.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/sync-promotion-boundary

Comment @coderabbitai help to get the list of available commands.

@stranske-keepalive

stranske-keepalive Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Automated Status Summary

Head SHA: f2671b6
Latest Runs: ⏳ pending — Gate
Required contexts: summary
Required: core tests (3.12): ⏳ pending, core tests (3.13): ⏳ pending, docker smoke: ⏳ pending, gate: ⏳ pending

Workflow / Job Result Logs
(no jobs reported) ⏳ pending

Coverage Overview

  • Coverage history entries: 1

Coverage Trend

Metric Value
Current 76.27%
Baseline 85.00%
Delta -8.73%
Minimum 70.00%
Status ✅ Pass

Top Coverage Hotspots (lowest coverage)

File Coverage Missing
scripts/issue_dedup_smoke.py 0.0% 4
scripts/runner_lib/__main__.py 0.0% 3
scripts/validate_template_sync.py 0.0% 81
scripts/langchain/topic_splitter.py 19.1% 57
tools/codex_log_analyzer.py 19.6% 140
scripts/repo_review_round2_runner.py 25.1% 344
scripts/prune_agent_stubs.py 39.7% 26
scripts/repo_review_round1_runner.py 40.7% 133
tools/ensure_workflow_timeout_variables.py 42.1% 74
scripts/sync_label_docs.py 42.9% 64
tools/discover_model_catalog.py 44.8% 55
scripts/repo_review_backlog_scan.py 45.3% 116
scripts/repo_review_body_writer.py 46.5% 86
tools/codex_session_analyzer.py 47.9% 59
scripts/create_verifier_labels.py 48.3% 58

Low Coverage Files (<50.0%)

File Coverage Missing
scripts/issue_dedup_smoke.py 0.0% 4
scripts/runner_lib/__main__.py 0.0% 3
scripts/validate_template_sync.py 0.0% 81
scripts/langchain/topic_splitter.py 19.1% 57
tools/codex_log_analyzer.py 19.6% 140
scripts/repo_review_round2_runner.py 25.1% 344
scripts/prune_agent_stubs.py 39.7% 26
scripts/repo_review_round1_runner.py 40.7% 133
tools/ensure_workflow_timeout_variables.py 42.1% 74
scripts/sync_label_docs.py 42.9% 64
tools/discover_model_catalog.py 44.8% 55
scripts/repo_review_backlog_scan.py 45.3% 116
scripts/repo_review_body_writer.py 46.5% 86
tools/codex_session_analyzer.py 47.9% 59
scripts/create_verifier_labels.py 48.3% 58

Updated automatically; will refresh on subsequent CI/Docker completions.


Keepalive checklist

Scope

No scope information available

Tasks

  • No tasks defined

Acceptance criteria

  • No acceptance criteria defined

Comment thread .github/workflows/maint-68-sync-consumer-repos.yml Fixed
Comment thread .github/workflows/maint-68-sync-consumer-repos.yml Fixed
Comment thread .github/workflows/maint-68-sync-consumer-repos.yml Fixed
@stranske
stranske temporarily deployed to agent-high-privilege August 11, 2026 04:35 — with GitHub Actions Inactive
@stranske
stranske temporarily deployed to agent-high-privilege August 11, 2026 04:42 — with GitHub Actions Inactive
@stranske
stranske marked this pull request as ready for review August 11, 2026 04:50
@stranske
stranske temporarily deployed to agent-high-privilege August 11, 2026 04:50 — with GitHub Actions Inactive
@stranske
stranske enabled auto-merge August 11, 2026 04:50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a36357f17a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


states: dict[str, dict[str, object]] = {}
for repo in repos:
repo_expected_branch = SYNC_CANDIDATE_BRANCH if repo in candidate_repos else expected_branch

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bind candidate PR coverage to the current plan

When a template or manifest change creates a new plan_id while a canary still has a recently updated PR on sync/workflows-candidate, this branch substitution makes that old PR count as covered; the subsequent checks validate only branch, head repo, and age, because fetch_open_sync_prs does not retrieve or validate the PR body's plan_id. Health 68 can therefore exit successfully and suppress the drift issue even though the candidate PR belongs to the previous plan. Parse the sync metadata and require its plan ID to equal current_plan_id before granting coverage.

Useful? React with 👍 / 👎.

Comment on lines +111 to +112
if not repos or not repos <= set(registered_repos):
raise ValueError("consumer sync canaries must be registered repositories")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow drift checks for a selected repository subset

When Health 68 is manually dispatched with its documented repos input containing only one or a few repositories, registered_repos here is actually that selected subset, so requiring every configured canary to be contained in it rejects almost every targeted check as an invalid canary configuration. For example, checking only stranske/Ready always fails before comparing any files. Validate the canary configuration against the authoritative fleet separately, then intersect the canaries with the repositories being checked.

Useful? React with 👍 / 👎.

Comment on lines +278 to +280
Do not wait for consumer CI in that workflow. Run Maint 71 later with
`active_sync_hash=candidate` to publish `sync-canary-evidence.json`, then invoke
Maint 68 with `phase=promote` and that artifact's JSON as

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use the declared Maint 71 input name

The Maint 71 workflow declares the input as sync_hash in .github/workflows/maint-71-merge-sync-prs.yml, not active_sync_hash. The local gh workflow run --help confirms that -f supplies workflow inputs as key=value, so an operator following this instruction cannot target sync/workflows-candidate and will receive an unexpected-input dispatch error. Change this guidance to sync_hash=candidate and show the corresponding dispatch flag.

Useful? React with 👍 / 👎.

@stranske
stranske merged commit f2671b6 into main Aug 11, 2026
78 of 79 checks passed
@stranske
stranske deleted the agent/sync-promotion-boundary branch August 11, 2026 04:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 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 @.github/workflows/maint-68-sync-consumer-repos.yml:
- Around line 806-820: Update the selected PR lookup and candidate-rotation
validation to retrieve the PR head repository full name, then require it to
equal the matrix repository value before accepting rotation. Use this trusted
head repository value rather than the PR-controlled record.repository field,
while preserving the existing terminal-disposition and candidate-plan checks in
isStableCandidate.

In `@docs/ops/CONSUMER_REPO_MAINTENANCE.md`:
- Around line 283-285: Update the promotion-scope documentation in
CONSUMER_REPO_MAINTENANCE.md to distinguish unfiltered and filtered promotions:
state that no-filter promotions target all registered non-canary repositories,
while filtered promotions target only the selected registered non-canaries.

In `@scripts/check_consumer_sync_drift.py`:
- Around line 100-113: Update resolve_candidate_repos and its caller to validate
configured canaries against the complete registered fleet before intersecting
them with the requested --repos subset. Preserve rejection of unregistered
canaries, return only canaries selected by the requested targets, and add
coverage for a one-canary --repos run when other configured canaries exist.

In `@scripts/select_consumer_sync_phase.py`:
- Around line 157-163: Update the selected_repos validation before assigning
selected to detect duplicate manual canary repository names and raise
PhaseSelectionError instead of allowing repeated entries. Preserve the existing
non-canary validation and error behavior, and add a test covering duplicate
canary input.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 879198e4-c541-480d-a871-122adaab1447

📥 Commits

Reviewing files that changed from the base of the PR and between e63314d and a36357f.

📒 Files selected for processing (11)
  • .github/scripts/__tests__/sync_pr_merge_contract.test.js
  • .github/workflows/maint-68-sync-consumer-repos.yml
  • docs/WORKFLOW_GUIDE.md
  • docs/ci/WORKFLOWS.md
  • docs/ci/WORKFLOW_SYSTEM.md
  • docs/ops/CONSUMER_REPO_MAINTENANCE.md
  • scripts/check_consumer_sync_drift.py
  • scripts/select_consumer_sync_phase.py
  • tests/scripts/test_check_consumer_sync_drift.py
  • tests/scripts/test_select_consumer_sync_phase.py
  • tests/workflows/test_sync_manifest_delivery.py

Comment on lines +806 to +820
const isStableCandidate =
process.env.SYNC_PHASE === "canary" &&
process.env.SYNC_BRANCH === "sync/workflows-candidate";
if (isStableCandidate) {
if (record.repository !== (process.env.DELIVERY_REPOSITORY || "")) {
process.stdout.write("false repository_mismatch");
process.exit(0);
}
if (record.terminal_disposition) {
process.stdout.write(`false terminal:${record.terminal_disposition}`);
process.exit(0);
}
process.stdout.write("true candidate_plan_rotation");
process.exit(0);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 6 \
  'gh pr list --head|isConsumerOpenPr|head\.repo|head_repo|full_name|candidate_plan_rotation' \
  .github/workflows/maint-68-sync-consumer-repos.yml .github/scripts

Repository: stranske/Workflows

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- workflow lookup and refresh path ---'
sed -n '710,855p' .github/workflows/maint-68-sync-consumer-repos.yml

printf '%s\n' '--- isConsumerOpenPr implementation ---'
sed -n '390,455p' .github/scripts/sync_tracker_state/index.js

printf '%s\n' '--- all workflow copies ---'
find .github/workflows templates/consumer-repo/.github/workflows -maxdepth 1 -type f \
  \( -name 'maint-68-sync-consumer-repos.yml' -o -name 'maint-68-sync-consumer-repos.yaml' \) -print

Repository: stranske/Workflows

Length of output: 9627


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- workflow setup and refresh execution ---'
rg -n -C 8 \
  'actions/checkout|cd consumer|gh pr list|gh pr view|gh pr edit|existing_refreshable|existing_pr|git fetch origin' \
  .github/workflows/maint-68-sync-consumer-repos.yml

printf '%s\n' '--- template workflow inventory ---'
find templates -type f 2>/dev/null | head -80 || true
rg -n 'maint-68-sync-consumer-repos|sync/workflows-candidate' templates .github/workflows 2>/dev/null || true

printf '%s\n' '--- representative acceptance-path verifier ---'
python3 - <<'PY'
import re

workflow = open('.github/workflows/maint-68-sync-consumer-repos.yml', encoding='utf-8').read()
helper = open('.github/scripts/sync_tracker_state/index.js', encoding='utf-8').read()

lookup = re.search(
    r'existing_pr=\$\(gh pr list --head "\$branch_name" --json number -q \'\.\[0\]\.number\'',
    workflow,
)
view_body_only = 'gh pr view "$existing_pr" --json body -q .body' in workflow
fetch_origin_branch = 'git fetch origin "$branch_name"' in workflow
helper_uses_head_repo = bool(re.search(r'head\?\.repo|head\.repo|headRepo|full_name', helper[helper.index('async function isConsumerOpenPr'):helper.index('function formatStuckWindowMarker')]))
helper_returns_boolean = 'return pulls.some' in helper[helper.index('async function isConsumerOpenPr'):helper.index('function formatStuckWindowMarker')]

def candidate_accepts(record, delivery_repo):
    return (
        record.get('repository') == delivery_repo
        and not record.get('terminal_disposition')
    )

cases = [
    ('consumer PR', {'repository': 'owner/consumer', 'terminal_disposition': ''}),
    ('fork PR with forged body', {'repository': 'owner/consumer', 'terminal_disposition': ''}),
]
print({
    'lookup_requests_only_number': bool(lookup),
    'body_query_omits_head_repo': view_body_only,
    'origin_fetches_branch_before_refresh': fetch_origin_branch,
    'isConsumerOpenPr_reads_head_repo': helper_uses_head_repo,
    'isConsumerOpenPr_returns_only_match_boolean': helper_returns_boolean,
    'candidate_body_acceptance': {
        name: candidate_accepts(record, 'owner/consumer')
        for name, record in cases
    },
})
PY

Repository: stranske/Workflows

Length of output: 263


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- workflow setup and refresh execution ---'
rg -n -C 8 'actions/checkout|cd consumer|gh pr list|gh pr view|gh pr edit|existing_refreshable|existing_pr|git fetch origin' \
  .github/workflows/maint-68-sync-consumer-repos.yml || true

printf '%s\n' '--- template workflow inventory ---'
if [ -d templates ]; then
  find templates -type f | head -80
else
  echo 'templates directory is absent'
fi
rg -n 'maint-68-sync-consumer-repos|sync/workflows-candidate' templates .github/workflows 2>/dev/null || true

printf '%s\n' '--- representative acceptance-path verifier ---'
python3 - <<'PY'
import re

workflow = open('.github/workflows/maint-68-sync-consumer-repos.yml', encoding='utf-8').read()
helper = open('.github/scripts/sync_tracker_state/index.js', encoding='utf-8').read()

helper_start = helper.index('async function isConsumerOpenPr')
helper_end = helper.index('function formatStuckWindowMarker')
helper_body = helper[helper_start:helper_end]

lookup = re.search(
    r'existing_pr=\$\(gh pr list --head "\$branch_name" --json number -q',
    workflow,
)
print({
    'lookup_requests_only_number': bool(lookup),
    'body_query_omits_head_repo': 'gh pr view "$existing_pr" --json body -q .body' in workflow,
    'origin_fetches_branch_before_refresh': 'git fetch origin "$branch_name"' in workflow,
    'isConsumerOpenPr_reads_head_repo': bool(re.search(r'head\?\.repo|head\.repo|headRepo|full_name', helper_body)),
    'isConsumerOpenPr_returns_only_match_boolean': 'return pulls.some' in helper_body,
})

def candidate_accepts(record, delivery_repo):
    return (
        record.get('repository') == delivery_repo
        and not record.get('terminal_disposition')
    )

print({
    'same_repo_record': candidate_accepts(
        {'repository': 'owner/consumer', 'terminal_disposition': ''},
        'owner/consumer',
    ),
    'fork_pr_with_forged_same_repo_record': candidate_accepts(
        {'repository': 'owner/consumer', 'terminal_disposition': ''},
        'owner/consumer',
    ),
})
PY

Repository: stranske/Workflows

Length of output: 5077


🌐 Web query:

GitHub CLI gh pr list --head branch name fork pull requests documentation

💡 Result:

To list pull requests filtered by a specific head branch name in the GitHub CLI, use the --head flag with the gh pr list command [1][2]. The command syntax is: gh pr list --head Key details regarding the --head flag: - It filters pull requests by the specified head branch name [1][3]. - It does not support the ": " syntax (e.g., owner:branch) [1][4]. - The flag is case-sensitive and requires the exact branch name [1]. For example, to list all open pull requests where the head branch is named "feature-typo", run: gh pr list --head "feature-typo" This command works for pull requests regardless of whether they originate from a fork or the main repository, provided the branch name matches the specified filter [1][5]. If you need to filter by a specific repository (such as a fork) in addition to the branch name, you can use the -R or --repo flag to specify the target repository in the format [HOST/]OWNER/REPO [5][3].

Citations:


Validate the selected PR’s head repository before candidate rotation.

gh pr list --head "$branch_name" also matches fork PRs, but the lookup requests only the PR number. Candidate eligibility then trusts the PR-controlled record.repository field. Require the selected PR’s head.repo.full_name to equal ${{ matrix.repo }} before accepting rotation. isConsumerOpenPr is insufficient because it returns only a branch-match boolean.

🤖 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 @.github/workflows/maint-68-sync-consumer-repos.yml around lines 806 - 820,
Update the selected PR lookup and candidate-rotation validation to retrieve the
PR head repository full name, then require it to equal the matrix repository
value before accepting rotation. Use this trusted head repository value rather
than the PR-controlled record.repository field, while preserving the existing
terminal-disposition and candidate-plan checks in isStableCandidate.

Source: Path instructions

Comment on lines 283 to 285
threads. A successful promotion targets all registered non-canary repositories
once every configured canary has current, green, review-clear evidence for the
same plan.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- candidate files and references ---'
rg -n --glob '!node_modules' --glob '!dist' --glob '!build' \
  'select_phase|selected_repos|target_repos|registered non-canary|successful promotion|canary' .
printf '%s\n' '--- documentation context ---'
file=$(git ls-files | grep -F 'docs/ops/CONSUMER_REPO_MAINTENANCE.md' | head -n1)
[ -n "$file" ]
sed -n '265,295p' "$file"
sed -n '460,490p' "$file"

Repository: stranske/Workflows

Length of output: 18670


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- selector implementation ---'
sed -n '120,188p' scripts/select_consumer_sync_phase.py
printf '%s\n' '--- selector tests ---'
sed -n '100,155p' tests/scripts/test_select_consumer_sync_phase.py
printf '%s\n' '--- workflow input and call path ---'
rg -n -A12 -B8 'selected_repos|repos:|select_phase\(' .github scripts tests \
  --glob '*.yml' --glob '*.yaml' --glob '*.py'

Repository: stranske/Workflows

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import ast

path = Path("scripts/select_consumer_sync_phase.py")
tree = ast.parse(path.read_text(encoding="utf-8"))
fn = next(
    node for node in tree.body
    if isinstance(node, ast.FunctionDef) and node.name == "select_phase"
)

assignments = {}
for node in ast.walk(fn):
    if isinstance(node, ast.Assign) and len(node.targets) == 1:
        target = node.targets[0]
        if isinstance(target, ast.Name):
            assignments[target.id] = ast.unparse(node.value)

assert "target_repos if selected_repos is not None else registered_repos" in assignments["target_repos"]
assert "[repo for repo in target_repos if repo not in canary_repos]" in assignments["selected"]
print("source rule: target_repos = selected_repos when provided; promote filters that list to non-canaries")

def promotion_targets(registered, canaries, selected_repos=None):
    target_repos = selected_repos if selected_repos is not None else registered
    return [repo for repo in target_repos if repo not in canaries]

registered = ["canary-a", "canary-b", "consumer-a", "consumer-b"]
canaries = ["canary-a", "canary-b"]
assert promotion_targets(registered, canaries) == ["consumer-a", "consumer-b"]
assert promotion_targets(registered, canaries, ["consumer-b"]) == ["consumer-b"]
print("behavior: no filter targets all non-canaries; a supplied filter narrows promotion")
PY

Repository: stranske/Workflows

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import ast

tree = ast.parse(Path("scripts/select_consumer_sync_phase.py").read_text(encoding="utf-8"))
fn = next(node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "select_phase")
for node in ast.walk(fn):
    if isinstance(node, ast.Assign) and len(node.targets) == 1:
        target = node.targets[0]
        if isinstance(target, ast.Name) and target.id in {"target_repos", "selected"}:
            print(f"{target.id} = {ast.unparse(node.value)}")
PY

Repository: stranske/Workflows

Length of output: 373


Document filtered promotion scope.

A no-filter promotion targets all registered non-canaries. A filtered promotion targets only the selected registered non-canaries.

🤖 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 `@docs/ops/CONSUMER_REPO_MAINTENANCE.md` around lines 283 - 285, Update the
promotion-scope documentation in CONSUMER_REPO_MAINTENANCE.md to distinguish
unfiltered and filtered promotions: state that no-filter promotions target all
registered non-canary repositories, while filtered promotions target only the
selected registered non-canaries.

Comment on lines +100 to +113
def resolve_candidate_repos(path: str, registered_repos: list[str]) -> set[str]:
"""Load the bounded canary set used by Maint 68 candidate branches."""
config = json.loads(Path(path).read_text(encoding="utf-8"))
canaries = config.get("canaries") if isinstance(config, dict) else None
if not isinstance(canaries, list):
raise ValueError("consumer sync canary config has no canaries list")
repos = {
str(item.get("repo", ""))
for item in canaries
if isinstance(item, dict) and item.get("repo")
}
if not repos or not repos <= set(registered_repos):
raise ValueError("consumer sync canaries must be registered repositories")
return repos

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate canaries against the fleet, not the requested subset.

Line 111 requires every configured canary to exist in repos. Line 918 passes the --repos target subset as repos. A run for one configured canary fails if the configuration contains another canary.

Load the full registered fleet for configuration validation. Then intersect the validated canary set with the requested target repos. Add a test for a one-canary --repos run.

As per path instructions, “Prioritize correctness, error handling, and test coverage.”

Also applies to: 917-919

🤖 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 `@scripts/check_consumer_sync_drift.py` around lines 100 - 113, Update
resolve_candidate_repos and its caller to validate configured canaries against
the complete registered fleet before intersecting them with the requested
--repos subset. Preserve rejection of unregistered canaries, return only
canaries selected by the requested targets, and add coverage for a one-canary
--repos run when other configured canaries exist.

Source: Path instructions

Comment on lines +157 to +163
if selected_repos is not None:
non_canaries = [repo for repo in selected_repos if repo not in canary_repos]
if non_canaries:
raise PhaseSelectionError(
"canary_selection_contains_non_canary:" + ",".join(non_canaries)
)
selected = selected_repos

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reject duplicate manual canary repositories.

Line 163 preserves duplicate values in selected_repos. A manual input such as canary-a,canary-a creates concurrent sync jobs for the same repository and stable candidate branch. The jobs can race during PR refresh and force-with-lease push.

Reject duplicate values before assigning selected. Add a duplicate-selection test.

As per path instructions, “Prioritize correctness, error handling, and test coverage.”

🤖 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 `@scripts/select_consumer_sync_phase.py` around lines 157 - 163, Update the
selected_repos validation before assigning selected to detect duplicate manual
canary repository names and raise PhaseSelectionError instead of allowing
repeated entries. Preserve the existing non-canary validation and error
behavior, and add a test covering duplicate canary input.

Source: Path instructions

@stranske stranske added the verify:compare Compare multiple LLM evaluations label Aug 11, 2026
@stranske
stranske temporarily deployed to agent-high-privilege August 11, 2026 10:40 — with GitHub Actions Inactive
@github-actions

Copy link
Copy Markdown
Contributor

Provider Comparison Report

Provider Summary

Provider Model Verdict Confidence Summary
openai gpt-5.6-terra PASS 86% The changes implement the staged synchronization boundary: canary-phase manual repository filters are constrained to configured canaries, canary repairs use the stable sync/workflows-candidate bran...
anthropic claude-sonnet-5 CONCERNS N/A Review the PR manually or re-run once LLM credentials are available.
📋 Full Provider Details (click to expand)

openai

  • Model: gpt-5.6-terra
  • Verdict: PASS
  • Confidence: 86%
  • Scores:
    • Correctness: 9.0/10
    • Completeness: 9.0/10
    • Quality: 9.0/10
    • Testing: 8.0/10
    • Risks: 8.0/10
  • Summary: The changes implement the staged synchronization boundary: canary-phase manual repository filters are constrained to configured canaries, canary repairs use the stable sync/workflows-candidate branch, and non-canary fan-out uses template-hash-named branches. The drift and promotion handoff changes align Maint 71 with candidate-based active sync state and keep remediation from promoting independently. Documentation has been updated for the candidate/evidence/promotion process. Targeted JavaScript and Python tests cover candidate hash/branch normalization, PR selection, phase selection, drift behavior, and manifest delivery. The implementation is readable and uses explicit phase-derived branch selection, with no material correctness, security, or compatibility issue evident in the change set.

anthropic

  • Model: claude-sonnet-5
  • Verdict: CONCERNS
  • Confidence: N/A
  • Summary: Review the PR manually or re-run once LLM credentials are available.
  • Concerns:
    • LLM evaluation could not run.
  • Error: LLM invocation failed: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.'}, 'request_id': 'req_011CdvpxtDsxwcP47L2FRCDv'}

Agreement

  • No clear areas of agreement.

Disagreement

Dimension openai anthropic
Verdict PASS CONCERNS

Unique Insights

  • openai: The changes implement the staged synchronization boundary: canary-phase manual repository filters are constrained to configured canaries, canary repairs use the stable sync/workflows-candidate branch, and non-canary fan-out uses template-hash-named branches. The drift and promotion handoff change...
  • anthropic: LLM evaluation could not run.

🔍 LangSmith Traces

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex codex-automation verify:compare Compare multiple LLM evaluations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants