Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci-self-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ on:

permissions:
contents: read
security-events: write # bats `self-test-routing` test asserts this is present

concurrency:
group: ci-self-test-${{ github.event.pull_request.number || github.ref }}
Expand Down
41 changes: 37 additions & 4 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# deploy.yml — event entries for the deploy domain.
# Routes CI/release completions to stg.yml and prd.yml.
# Observability triggers live in observability.yml.
#
# image-digest plumbing:
# - workflow_dispatch: caller passes inputs.image-digest (and stg-* for prd)
# - workflow_run: ci.yml / release.yml are expected to write the digest
# into vars.LAST_CI_IMAGE_DIGEST (etc.) before completing
# so downstream deploys can read it; until that wiring
# lands, workflow_run dispatches read the same vars.*
# with sensible empty fallbacks.
name: deploy

on:
Expand All @@ -13,6 +21,21 @@ on:
required: false
type: string
default: stg
image-digest:
description: "sha256:... digest of the image to deploy. Required for stg/prd."
required: false
type: string
default: ""
stg-image-digest:
description: "Staging image digest (prd only — used for the safety gate)."
required: false
type: string
default: ""
stg-deploy-time:
description: "Staging deploy timestamp ISO 8601 (prd only — observation window start)."
required: false
type: string
default: ""

permissions:
contents: write
Expand All @@ -38,7 +61,11 @@ jobs:
with:
app-name: ${{ vars.APP_NAME || github.event.repository.name }}
image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }}
image-digest: ${{ vars.STG_IMAGE_DIGEST }}
# image-digest must reach the reusable so deploy steps build a real
# registry/owner/name@sha256:... reference. dispatch supplies it via
# inputs; workflow_run paths read vars.LAST_CI_IMAGE_DIGEST written
# by the upstream ci.yml on success.
image-digest: ${{ inputs.image-digest || vars.LAST_CI_IMAGE_DIGEST || '' }}
health-url: ${{ vars.STG_HEALTH_URL }}
runner: blacksmith-2vcpu-ubuntu-2404
deploy-type: ${{ vars.DEPLOY_TYPE || 'docker' }}
Expand All @@ -49,7 +76,9 @@ jobs:
compose-file: ${{ vars.COMPOSE_FILE || 'docker-compose.yml' }}
compose-project-dir: ${{ vars.COMPOSE_PROJECT_DIR || '~' }}
# secrets:inherit name-mismatches kebab-case → UPPER_SNAKE; map explicit.
# Forward every secret the reusable declares so SSH/k8s preflight passes.
secrets:
kubeconfig-stg: ${{ secrets.KUBECONFIG_STG }}
ssh-key-stg: ${{ secrets.SSH_KEY_STG }}
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
sentry-token: ${{ secrets.SENTRY_TOKEN }}
Expand All @@ -65,9 +94,13 @@ jobs:
with:
app-name: ${{ vars.APP_NAME || github.event.repository.name }}
image-name: ${{ vars.IMAGE_NAME || github.event.repository.name }}
image-digest: ${{ vars.PRD_IMAGE_DIGEST }}
stg-image-digest: ${{ vars.STG_IMAGE_DIGEST }}
stg-deploy-time: ${{ vars.STG_DEPLOY_TIME }}
# The 3 prd-specific safety-gate inputs MUST be set or the
# observation-window check is skipped silently. Source order:
# 1. workflow_dispatch input (manual override)
# 2. vars.* recorded by the upstream stg deploy on success
image-digest: ${{ inputs.image-digest || vars.LAST_RELEASE_IMAGE_DIGEST || '' }}
stg-image-digest: ${{ inputs.stg-image-digest || vars.LAST_STG_IMAGE_DIGEST || '' }}
stg-deploy-time: ${{ inputs.stg-deploy-time || vars.LAST_STG_DEPLOY_TIME || '' }}
Comment on lines +97 to +103

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify which workflows write/read the digest variables and whether LAST_RELEASE_IMAGE_DIGEST is guaranteed.
set -euo pipefail

echo "== Writers/readers of digest/time vars =="
rg -n -C2 'LAST_(CI|RELEASE|STG)_(IMAGE_DIGEST|DEPLOY_TIME)' .github/workflows

echo
echo "== Any explicit variable writes/updates in workflows =="
rg -n -C3 '(gh variable set|LAST_RELEASE_IMAGE_DIGEST|LAST_CI_IMAGE_DIGEST|LAST_STG_IMAGE_DIGEST|LAST_STG_DEPLOY_TIME)' .github/workflows

Repository: YiAgent/OpenCI

Length of output: 4708


🏁 Script executed:

cat -n .github/workflows/deploy.yml | sed -n '75,120p'

Repository: YiAgent/OpenCI

Length of output: 3088


🏁 Script executed:

# Check the reusable-prd.yml for input validation
fd -name 'reusable-prd.yml' .github/workflows && cat -n .github/workflows/reusable-prd.yml | head -80

echo
echo "== Looking for gh variable set commands =="
rg -n 'gh variable set' .github/workflows

Repository: YiAgent/OpenCI

Length of output: 351


🏁 Script executed:

# Find reusable-prd.yml
fd 'reusable-prd' .github/

echo
echo "== Check for variable writes (broader search) =="
rg 'LAST_RELEASE_IMAGE_DIGEST|LAST_STG_IMAGE_DIGEST|LAST_STG_DEPLOY_TIME' --type yaml

echo
echo "== Check for gh command usage anywhere =="
rg 'gh ' --type yaml | head -20

Repository: YiAgent/OpenCI

Length of output: 2311


🏁 Script executed:

cat -n .github/workflows/reusable-prd.yml | head -150

Repository: YiAgent/OpenCI

Length of output: 5696


🏁 Script executed:

cat -n .github/workflows/reusable-prd.yml | sed -n '150,300p'

Repository: YiAgent/OpenCI

Length of output: 7139


🏁 Script executed:

fd 'pre-check' .openci/actions 2>/dev/null || find . -name 'pre-check' -type d 2>/dev/null | head -5

Repository: YiAgent/OpenCI

Length of output: 82


🏁 Script executed:

cat -n ./actions/prd/pre-check/action.yml

Repository: YiAgent/OpenCI

Length of output: 4041


🏁 Script executed:

cat -n ./actions/prd/observe-window/action.yml
echo
echo "=== verify-version-align ==="
cat -n ./actions/prd/verify-version-align/action.yml

Repository: YiAgent/OpenCI

Length of output: 2854


🏁 Script executed:

cat -n ./actions/prd/observe-window/check.sh

Repository: YiAgent/OpenCI

Length of output: 2210


🏁 Script executed:

# Check if there are any other places where these vars are written/set
rg 'LAST_RELEASE_IMAGE_DIGEST|LAST_CI_IMAGE_DIGEST' --type bash

Repository: YiAgent/OpenCI

Length of output: 87


🏁 Script executed:

rg 'LAST_RELEASE_IMAGE_DIGEST|LAST_CI_IMAGE_DIGEST' -t sh

Repository: YiAgent/OpenCI

Length of output: 40


🏁 Script executed:

rg 'LAST_RELEASE_IMAGE_DIGEST|LAST_CI_IMAGE_DIGEST|LAST_STG' --type yaml --type json

Repository: YiAgent/OpenCI

Length of output: 40


Add non-empty validation for PRD digest inputs; harmonize fallback chain with STG.

Lines 101-103: The image-digest and stg-image-digest inputs can resolve to empty strings (if LAST_RELEASE_IMAGE_DIGEST and LAST_STG_IMAGE_DIGEST are unset), which silently bypass digest validation in verify-version-align since equal empty strings pass the equality check. Additionally, image-digest at line 101 lacks the vars.LAST_CI_IMAGE_DIGEST fallback that the STG deployment uses at line 68, reducing resilience when LAST_RELEASE_IMAGE_DIGEST is unavailable.

Add the missing fallback to line 101 and validate all three inputs are non-empty before running prd:

Suggested hardening diff
   prd:
     if: >-
       (github.event_name == 'workflow_run'
         && github.event.workflow_run.name == 'release'
         && github.event.workflow_run.conclusion == 'success')
-      || (github.event_name == 'workflow_dispatch' && inputs.mode == 'prd')
+      || (github.event_name == 'workflow_dispatch' && inputs.mode == 'prd')
+      && ((inputs.image-digest     || vars.LAST_RELEASE_IMAGE_DIGEST || vars.LAST_CI_IMAGE_DIGEST || '') != '')
+      && ((inputs.stg-image-digest || vars.LAST_STG_IMAGE_DIGEST     || '') != '')
+      && ((inputs.stg-deploy-time  || vars.LAST_STG_DEPLOY_TIME      || '') != '')
     uses: YiAgent/OpenCI/.github/workflows/reusable-prd.yml@cd1b427370ebacb56cc9c0b418d6d8985c9be539
     with:
       app-name:            ${{ vars.APP_NAME || github.event.repository.name }}
       image-name:          ${{ vars.IMAGE_NAME || github.event.repository.name }}
       # The 3 prd-specific safety-gate inputs MUST be set or the
       # observation-window check is skipped silently. Source order:
       #   1. workflow_dispatch input (manual override)
       #   2. vars.* recorded by the upstream stg deploy on success
-      image-digest:        ${{ inputs.image-digest     || vars.LAST_RELEASE_IMAGE_DIGEST || '' }}
+      image-digest:        ${{ inputs.image-digest     || vars.LAST_RELEASE_IMAGE_DIGEST || vars.LAST_CI_IMAGE_DIGEST || '' }}
🤖 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/deploy.yml around lines 97 - 103, Update the input
fallback and pre-checks so PRD digest validation can't be bypassed: add
vars.LAST_CI_IMAGE_DIGEST to the fallback chain for the image-digest input (so
image-digest uses inputs.image-digest || vars.LAST_RELEASE_IMAGE_DIGEST ||
vars.LAST_CI_IMAGE_DIGEST || ''), and then add a non-empty validation step
before invoking the prd path (or before calling verify-version-align) that fails
the workflow if any of image-digest, stg-image-digest, or stg-deploy-time
resolve to an empty string; ensure the check references the inputs/vars names
(image-digest, stg-image-digest, stg-deploy-time,
vars.LAST_RELEASE_IMAGE_DIGEST, vars.LAST_STG_IMAGE_DIGEST,
vars.LAST_CI_IMAGE_DIGEST) and prevents running verify-version-align/prd when
any are empty.

health-url: ${{ vars.PRD_HEALTH_URL }}
runner: blacksmith-2vcpu-ubuntu-2404
deploy-type: ${{ vars.DEPLOY_TYPE || 'docker' }}
Expand Down
52 changes: 43 additions & 9 deletions .github/workflows/on-main-bump-sha.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:
if: steps.check.outputs.skip != 'true'
run: bash scripts/bump-self-sha.sh

- name: Commit and open PR
- name: Commit, supersede older bumps, open PR
if: steps.check.outputs.skip != 'true'
env:
# gh CLI uses GH_TOKEN; same PAT used for checkout above so the
Expand All @@ -88,26 +88,60 @@ jobs:
NEW_SHA: ${{ steps.check.outputs.new_sha }}
OLD_SHA: ${{ steps.check.outputs.current_sha }}
run: |
set -euo pipefail

branch="chore/bump-self-sha-${NEW_SHA:0:8}"
short_old="${OLD_SHA:0:8}"
short_new="${NEW_SHA:0:8}"

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$branch"
git checkout -B "$branch"
git add manifest.yml .github/workflows/ actions/
git commit -m "chore(manifest): bump YiAgent/OpenCI SHA to ${short_new}" \
-m "Automated update from on-main-bump-sha workflow. old=${OLD_SHA} new=${NEW_SHA}"

git push origin "$branch"
# Force-with-lease so workflow re-runs (same branch name) succeed
# without trampling unrelated work.
git push --force-with-lease origin "$branch"

# 1) Close older auto-bump PRs and delete their branches.
gh pr list \
--state open \
--base main \
--json number,headRefName \
--jq '.[] | select(.headRefName | startswith("chore/bump-self-sha-"))
| select(.headRefName != "'"$branch"'")
| .number' \
| while read -r old_num; do
echo "::notice::Closing superseded bump PR #${old_num}"
gh pr close "$old_num" \
--comment "Superseded by \`${branch}\` (bump to ${short_new})." \
--delete-branch
done

# 2) Delete orphan auto-bump branches that never got a PR.
git ls-remote origin 'refs/heads/chore/bump-self-sha-*' \
| awk '{sub(/^refs\/heads\//,"",$2); print $2}' \
| while read -r ref; do
[ "$ref" = "$branch" ] && continue
echo "::notice::Deleting orphan bump branch ${ref}"
git push origin --delete "$ref" || true
done

# 3) Open (or reuse) the PR for the new branch.
# shellcheck disable=SC2016
printf 'Automated SHA bump: `%s` to `%s`\n\nThe YiAgent/OpenCI self-reference SHA in manifest.yml was stale. Updated to the latest main HEAD so all reusable workflow calls resolve correctly.\n\n> Generated by the on-main-bump-sha workflow.' \
"$short_old" "$short_new" > /tmp/pr-body.md

gh pr create \
--title "chore(manifest): bump YiAgent/OpenCI SHA to ${short_new}" \
--body-file /tmp/pr-body.md \
--base main \
--head "$branch" \
--label "chore" 2>/dev/null || true
existing="$(gh pr list --state open --head "$branch" --base main \
--json number --jq '.[0].number // ""')"
if [ -n "$existing" ]; then
echo "::notice::PR #${existing} already exists for ${branch}; skipping create"
else
gh pr create \
--title "chore(manifest): bump YiAgent/OpenCI SHA to ${short_new}" \
--body-file /tmp/pr-body.md \
--base main \
--head "$branch"
fi
2 changes: 1 addition & 1 deletion .github/workflows/reusable-agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# Inputs / secrets / outputs MUST stay byte-identical to the composite — a
# caller can swap one for the other without changing wiring.
# ─────────────────────────────────────────────────────────────────────────────
name: claude-harness
name: reusable-agent

on:
workflow_call:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/reusable-deps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# Security: we only call `gh pr merge --auto` (gated on branch protection
# rules); PR fields are not interpolated into shell commands.
# ─────────────────────────────────────────────────────────────────────────────
name: dep-auto-merge
name: reusable-deps

on:
workflow_call:
Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/reusable-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@ on:

permissions: {}

concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
# Reusable workflows must NOT redeclare the caller's concurrency group —
# GitHub's deadlock-detection cancels the inner run when both share it
# (see workflow-audit rule W03 / issue #68). Concurrency is owned by the
# caller (release.yml) instead.

jobs:
# ── marketplace tagging + GitHub Release ────────────────────────────────
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,10 @@ gate-context/
# act local testing
.act.env*.yml-e
*.yaml-e

# Claude Code runtime artifacts (per-developer, not project state)
.claude/worktrees/
.claude/scheduled_tasks.lock
.claude/agents/
.claude/projects/
.claude/todos/
Loading
Loading