# ============================================================================= # 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<> "$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 <> "$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