mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-06 09:26:52 +01:00
330 lines
No EOL
15 KiB
YAML
330 lines
No EOL
15 KiB
YAML
# =============================================================================
|
|
# KPS Frontier — BCQuality Fork Upstream Sync
|
|
# =============================================================================
|
|
#
|
|
# Path: kps-frontier/bcquality-fork/.github/workflows/upstream-sync.yml
|
|
# Purpose: Track upstream microsoft/BCQuality on a non-production branch;
|
|
# notify maintainer when upstream advances; gate promotion to fork's
|
|
# `main` behind a human-reviewed PR.
|
|
# Version: v0.2 — App-identity retrofit per KPS-SignalAgent-v0_1.md.
|
|
# Changes from v0.1:
|
|
# - Notification authenticates as kps-signal-agent[bot] via App
|
|
# installation token, not bare GITHUB_TOKEN.
|
|
# - Compare URL constructed on monitored fork, not upstream
|
|
# (fixes 404-when-fork-history-diverges per spec §6).
|
|
# - Structured event log emission per spec §14 (run.started,
|
|
# advance.absent, advance.detected, issue.opened, run.completed,
|
|
# run.failed).
|
|
# - Environment scope `signal-agent-runtime` for App private key.
|
|
# - Top-level `permissions: contents: write` retained (git push to
|
|
# upstream-tracking); `issues: write` dropped (App token covers).
|
|
# Classification: Internal — Selective Showcase: Private.
|
|
#
|
|
# Identity contract:
|
|
# - upstream-tracking branch push: bare GITHUB_TOKEN (workflow contents:write)
|
|
# - Issue creation: kps-signal-agent App installation token
|
|
# =============================================================================
|
|
|
|
name: upstream-sync
|
|
|
|
on:
|
|
schedule:
|
|
- cron: '0 14 * * 1' # Monday 14:00 UTC
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: write # required for git push to upstream-tracking branch
|
|
# issues: write removed — App token covers issue creation per ADR-002
|
|
|
|
jobs:
|
|
sync:
|
|
name: Sync upstream-tracking branch and notify
|
|
runs-on: ubuntu-24.04
|
|
environment: signal-agent-runtime
|
|
env:
|
|
AGENT_NAME: kps-signal-agent
|
|
AGENT_VERSION: '0.1'
|
|
MONITORED_REPO: ${{ github.repository }}
|
|
UPSTREAM_REPO: microsoft/BCQuality
|
|
steps:
|
|
- name: Checkout fork (full history)
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Configure git identity for push
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
|
|
- name: Fetch upstream
|
|
run: |
|
|
git remote add upstream "https://github.com/${UPSTREAM_REPO}.git"
|
|
git fetch upstream main
|
|
|
|
- name: Capture run start time
|
|
id: start_time
|
|
run: |
|
|
echo "epoch_ms=$(($(date -u +%s%N)/1000000))" >> "$GITHUB_OUTPUT"
|
|
echo "rfc3339=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Compute SHA delta and build payload
|
|
id: delta
|
|
run: |
|
|
set -euo pipefail
|
|
UPSTREAM_SHA=$(git rev-parse upstream/main)
|
|
FORK_MAIN_SHA=$(git rev-parse origin/main)
|
|
|
|
if git show-ref --verify --quiet refs/remotes/origin/upstream-tracking; then
|
|
LAST_TRACKED=$(git rev-parse origin/upstream-tracking)
|
|
else
|
|
LAST_TRACKED=""
|
|
fi
|
|
|
|
COMMITS_AHEAD=$(git rev-list --count origin/main..upstream/main)
|
|
COMMIT_SUBJECTS=$(git log origin/main..upstream/main --pretty=format:'%s' -n 20 || true)
|
|
|
|
UPSTREAM_SHA_SHORT="${UPSTREAM_SHA:0:7}"
|
|
FORK_MAIN_SHA_SHORT="${FORK_MAIN_SHA:0:7}"
|
|
|
|
if [[ "$UPSTREAM_SHA" == "${LAST_TRACKED:-}" ]]; then
|
|
TRACKING_ADVANCED="false"
|
|
else
|
|
TRACKING_ADVANCED="true"
|
|
fi
|
|
|
|
{
|
|
echo "upstream_sha=$UPSTREAM_SHA"
|
|
echo "fork_main_sha=$FORK_MAIN_SHA"
|
|
echo "upstream_sha_short=$UPSTREAM_SHA_SHORT"
|
|
echo "fork_main_sha_short=$FORK_MAIN_SHA_SHORT"
|
|
echo "commits_ahead=$COMMITS_AHEAD"
|
|
echo "tracking_advanced=$TRACKING_ADVANCED"
|
|
echo 'commit_subjects<<COMMIT_SUBJECTS_EOF'
|
|
echo "$COMMIT_SUBJECTS"
|
|
echo 'COMMIT_SUBJECTS_EOF'
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
echo "Delta: upstream=${UPSTREAM_SHA_SHORT}, fork_main=${FORK_MAIN_SHA_SHORT}, commits_ahead=${COMMITS_AHEAD}, tracking_advanced=${TRACKING_ADVANCED}"
|
|
|
|
- name: Push upstream-tracking branch
|
|
if: steps.delta.outputs.tracking_advanced == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
git push origin upstream/main:refs/heads/upstream-tracking --force-with-lease
|
|
|
|
- name: Emit signal_agent.run.started event
|
|
run: |
|
|
jq -n -c \
|
|
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
--arg agent "$AGENT_NAME" \
|
|
--arg agent_version "$AGENT_VERSION" \
|
|
--arg run_id "${{ github.run_id }}" \
|
|
--arg monitored_repo "$MONITORED_REPO" \
|
|
--arg trigger_type "${{ github.event_name }}" \
|
|
--arg upstream_repo "$UPSTREAM_REPO" \
|
|
'{timestamp:$ts, agent:$agent, agent_version:$agent_version, event_type:"signal_agent.run.started", workflow_run_id:$run_id, monitored_repo:$monitored_repo, payload:{trigger_type:$trigger_type, upstream_repo:$upstream_repo}}'
|
|
|
|
- name: Emit signal_agent.advance.absent event
|
|
if: steps.delta.outputs.commits_ahead == '0'
|
|
run: |
|
|
jq -n -c \
|
|
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
--arg agent "$AGENT_NAME" \
|
|
--arg agent_version "$AGENT_VERSION" \
|
|
--arg run_id "${{ github.run_id }}" \
|
|
--arg monitored_repo "$MONITORED_REPO" \
|
|
--arg upstream_sha "${{ steps.delta.outputs.upstream_sha }}" \
|
|
--arg fork_main_sha "${{ steps.delta.outputs.fork_main_sha }}" \
|
|
'{timestamp:$ts, agent:$agent, agent_version:$agent_version, event_type:"signal_agent.advance.absent", workflow_run_id:$run_id, monitored_repo:$monitored_repo, payload:{upstream_sha:$upstream_sha, fork_main_sha:$fork_main_sha}}'
|
|
|
|
- name: Mint signal-agent installation token
|
|
if: steps.delta.outputs.commits_ahead != '0'
|
|
id: app_token
|
|
uses: actions/create-github-app-token@v1
|
|
with:
|
|
app-id: ${{ vars.KPS_SIGNAL_AGENT_APP_ID }}
|
|
private-key: ${{ secrets.KPS_SIGNAL_AGENT_PRIVATE_KEY }}
|
|
owner: kps-frontier
|
|
repositories: bcquality-fork
|
|
|
|
- name: Emit signal_agent.advance.detected event
|
|
if: steps.delta.outputs.commits_ahead != '0'
|
|
run: |
|
|
jq -n -c \
|
|
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
--arg agent "$AGENT_NAME" \
|
|
--arg agent_version "$AGENT_VERSION" \
|
|
--arg run_id "${{ github.run_id }}" \
|
|
--arg monitored_repo "$MONITORED_REPO" \
|
|
--arg upstream_sha "${{ steps.delta.outputs.upstream_sha }}" \
|
|
--arg fork_main_sha "${{ steps.delta.outputs.fork_main_sha }}" \
|
|
--argjson commits_ahead ${{ steps.delta.outputs.commits_ahead }} \
|
|
'{timestamp:$ts, agent:$agent, agent_version:$agent_version, event_type:"signal_agent.advance.detected", workflow_run_id:$run_id, monitored_repo:$monitored_repo, payload:{upstream_sha:$upstream_sha, fork_main_sha:$fork_main_sha, commits_ahead:$commits_ahead}}'
|
|
|
|
- name: Open upstream-sync issue (Signal Agent)
|
|
if: steps.delta.outputs.commits_ahead != '0'
|
|
id: issue_create
|
|
env:
|
|
GH_TOKEN: ${{ steps.app_token.outputs.token }}
|
|
UPSTREAM_SHA: ${{ steps.delta.outputs.upstream_sha }}
|
|
FORK_MAIN_SHA: ${{ steps.delta.outputs.fork_main_sha }}
|
|
UPSTREAM_SHA_SHORT: ${{ steps.delta.outputs.upstream_sha_short }}
|
|
FORK_MAIN_SHA_SHORT: ${{ steps.delta.outputs.fork_main_sha_short }}
|
|
COMMITS_AHEAD: ${{ steps.delta.outputs.commits_ahead }}
|
|
COMMIT_SUBJECTS: ${{ steps.delta.outputs.commit_subjects }}
|
|
DETECTED_AT: ${{ steps.start_time.outputs.rfc3339 }}
|
|
TRIGGER_TYPE: ${{ github.event_name }}
|
|
RUN_ID: ${{ github.run_id }}
|
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
DETECTED_AT_DATE="${DETECTED_AT%%T*}"
|
|
|
|
# Compare URL constructed on the MONITORED FORK, not upstream.
|
|
# Per Signal Agent v0.1 §6: fork-side compare is reachable regardless
|
|
# of fork history; upstream-side compare would 404 if fork main is
|
|
# not reachable from upstream.
|
|
COMPARE_URL="https://github.com/${MONITORED_REPO}/compare/${FORK_MAIN_SHA}...${UPSTREAM_SHA}"
|
|
|
|
TITLE="BCQuality upstream advance: ${COMMITS_AHEAD} commits ahead (${DETECTED_AT_DATE})"
|
|
|
|
if [[ -n "${COMMIT_SUBJECTS:-}" ]]; then
|
|
COMMITS_RENDERED=$(echo "$COMMIT_SUBJECTS" | sed 's/^/- /')
|
|
else
|
|
COMMITS_RENDERED="_(commit subjects not enumerated)_"
|
|
fi
|
|
|
|
BODY=$(cat <<EOF
|
|
## Upstream advance detected
|
|
|
|
**Upstream:** \`${UPSTREAM_REPO}\`
|
|
**Monitored fork:** \`${MONITORED_REPO}\`
|
|
**Branch comparison:** \`upstream-tracking\` vs \`main\`
|
|
**Commits ahead:** ${COMMITS_AHEAD}
|
|
**Latest upstream SHA:** \`${UPSTREAM_SHA_SHORT}\`
|
|
**Fork main SHA:** \`${FORK_MAIN_SHA_SHORT}\`
|
|
**Diff:** [\`${FORK_MAIN_SHA_SHORT}..${UPSTREAM_SHA_SHORT}\`](${COMPARE_URL}) — ${COMMITS_AHEAD} commits, full file-level diff
|
|
**Detected:** ${DETECTED_AT}
|
|
**Trigger:** ${TRIGGER_TYPE}
|
|
**Workflow run:** [#${RUN_ID}](${RUN_URL})
|
|
|
|
### Commits in delta (most recent first, up to 20)
|
|
|
|
${COMMITS_RENDERED}
|
|
|
|
### Next steps
|
|
|
|
A platform admin should review the \`upstream-tracking\` branch on \`${MONITORED_REPO}\` and decide whether to fork-merge to \`main\`. See Approved External Sources Inventory v0.1 §4.1 (BCQuality entry) and Roadmap v0.2 §7.18 for review procedure.
|
|
|
|
---
|
|
|
|
*Generated by \`kps-signal-agent[bot]\` via \`upstream-sync.yml\`. This is a deterministic Phase 1 tripwire — no classification or recommendation has been performed. See \`KPS-SignalAgent-v0_1.md\`.*
|
|
EOF
|
|
)
|
|
|
|
# Dedup: skip if an open upstream-sync issue already exists for this upstream SHA.
|
|
EXISTING_JSON=$(gh issue list \
|
|
--repo "${MONITORED_REPO}" \
|
|
--label upstream-sync \
|
|
--state open \
|
|
--search "${UPSTREAM_SHA}" \
|
|
--json number)
|
|
|
|
EXISTING_COUNT=$(echo "$EXISTING_JSON" | jq 'length')
|
|
|
|
if [[ "${EXISTING_COUNT}" -gt 0 ]]; then
|
|
EXISTING_NUMBER=$(echo "$EXISTING_JSON" | jq '.[0].number')
|
|
echo "Open upstream-sync Issue #${EXISTING_NUMBER} already exists for ${UPSTREAM_SHA}; not duplicating."
|
|
{
|
|
echo "issue_number=${EXISTING_NUMBER}"
|
|
echo "issue_url="
|
|
echo "issue_title="
|
|
echo "deduped=true"
|
|
} >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
ISSUE_URL=$(gh issue create \
|
|
--repo "${MONITORED_REPO}" \
|
|
--label upstream-sync \
|
|
--title "${TITLE}" \
|
|
--body "${BODY}")
|
|
|
|
ISSUE_NUMBER=$(echo "${ISSUE_URL}" | awk -F/ '{print $NF}')
|
|
|
|
{
|
|
echo "issue_url=${ISSUE_URL}"
|
|
echo "issue_number=${ISSUE_NUMBER}"
|
|
echo "issue_title=${TITLE}"
|
|
echo "deduped=false"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Emit signal_agent.issue.opened event
|
|
if: steps.delta.outputs.commits_ahead != '0' && steps.issue_create.outputs.deduped == 'false'
|
|
run: |
|
|
jq -n -c \
|
|
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
--arg agent "$AGENT_NAME" \
|
|
--arg agent_version "$AGENT_VERSION" \
|
|
--arg run_id "${{ github.run_id }}" \
|
|
--arg monitored_repo "$MONITORED_REPO" \
|
|
--argjson issue_number ${{ steps.issue_create.outputs.issue_number }} \
|
|
--arg issue_url "${{ steps.issue_create.outputs.issue_url }}" \
|
|
--arg title "${{ steps.issue_create.outputs.issue_title }}" \
|
|
'{timestamp:$ts, agent:$agent, agent_version:$agent_version, event_type:"signal_agent.issue.opened", workflow_run_id:$run_id, monitored_repo:$monitored_repo, payload:{issue_number:$issue_number, issue_url:$issue_url, label:"upstream-sync", title:$title}}'
|
|
|
|
- name: Emit signal_agent.run.completed event
|
|
if: success()
|
|
run: |
|
|
set -euo pipefail
|
|
END_MS=$(($(date -u +%s%N)/1000000))
|
|
DURATION_MS=$((END_MS - ${{ steps.start_time.outputs.epoch_ms }}))
|
|
|
|
if [[ "${{ steps.delta.outputs.commits_ahead }}" == "0" ]]; then
|
|
jq -n -c \
|
|
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
--arg agent "$AGENT_NAME" \
|
|
--arg agent_version "$AGENT_VERSION" \
|
|
--arg run_id "${{ github.run_id }}" \
|
|
--arg monitored_repo "$MONITORED_REPO" \
|
|
--argjson duration_ms $DURATION_MS \
|
|
'{timestamp:$ts, agent:$agent, agent_version:$agent_version, event_type:"signal_agent.run.completed", workflow_run_id:$run_id, monitored_repo:$monitored_repo, payload:{advance_detected:false, duration_ms:$duration_ms}}'
|
|
else
|
|
jq -n -c \
|
|
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
--arg agent "$AGENT_NAME" \
|
|
--arg agent_version "$AGENT_VERSION" \
|
|
--arg run_id "${{ github.run_id }}" \
|
|
--arg monitored_repo "$MONITORED_REPO" \
|
|
--argjson issue_number ${{ steps.issue_create.outputs.issue_number }} \
|
|
--argjson duration_ms $DURATION_MS \
|
|
'{timestamp:$ts, agent:$agent, agent_version:$agent_version, event_type:"signal_agent.run.completed", workflow_run_id:$run_id, monitored_repo:$monitored_repo, payload:{advance_detected:true, issue_number:$issue_number, duration_ms:$duration_ms}}'
|
|
fi
|
|
|
|
- name: Emit signal_agent.run.failed event
|
|
if: failure()
|
|
run: |
|
|
jq -n -c \
|
|
--arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
--arg agent "$AGENT_NAME" \
|
|
--arg agent_version "$AGENT_VERSION" \
|
|
--arg run_id "${{ github.run_id }}" \
|
|
--arg monitored_repo "$MONITORED_REPO" \
|
|
'{timestamp:$ts, agent:$agent, agent_version:$agent_version, event_type:"signal_agent.run.failed", workflow_run_id:$run_id, monitored_repo:$monitored_repo, payload:{error_kind:"internal_error", error_message:"Workflow step failed; see job log for details.", stage:"unknown"}}'
|
|
|
|
- name: Summary
|
|
if: always()
|
|
run: |
|
|
if [[ "${{ job.status }}" == "success" ]]; then
|
|
if [[ "${{ steps.delta.outputs.commits_ahead }}" == "0" ]]; then
|
|
echo "::notice::No upstream changes (still at ${{ steps.delta.outputs.upstream_sha_short }})."
|
|
elif [[ "${{ steps.issue_create.outputs.deduped }}" == "true" ]]; then
|
|
echo "::notice::Upstream advanced ${{ steps.delta.outputs.commits_ahead }} commits, but Issue #${{ steps.issue_create.outputs.issue_number }} already open for ${{ steps.delta.outputs.upstream_sha_short }}. Skipped duplicate."
|
|
else
|
|
echo "::notice::Upstream advanced ${{ steps.delta.outputs.commits_ahead }} commits to ${{ steps.delta.outputs.upstream_sha_short }}. Issue created: ${{ steps.issue_create.outputs.issue_url }}"
|
|
fi
|
|
else
|
|
echo "::error::Workflow failed; see job log for details."
|
|
fi |