Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88e0f7ddf9 | |||
| 12ccf82454 |
Vendored
+1
@@ -0,0 +1 @@
|
||||
ghs_n2DXnoj38RccFYNlzH18XQ739bhr8e2w4BZK
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
# The current version of the config schema
|
||||
version: 1
|
||||
# What protocol to use when performing git operations. Supported values: ssh, https
|
||||
git_protocol: https
|
||||
# What editor gh should run when creating issues, pull requests, etc. If blank, will refer to environment.
|
||||
editor:
|
||||
# When to interactively prompt. This is a global config that cannot be overridden by hostname. Supported values: enabled, disabled
|
||||
prompt: enabled
|
||||
# A pager program to send command output to, e.g. "less". If blank, will refer to environment. Set the value to "cat" to disable the pager.
|
||||
pager:
|
||||
# Aliases allow you to create nicknames for gh commands
|
||||
aliases:
|
||||
co: pr checkout
|
||||
# The path to a unix socket through which send HTTP connections. If blank, HTTP traffic will be handled by net/http.DefaultTransport.
|
||||
http_unix_socket:
|
||||
# What web browser gh should use when opening URLs. If blank, will refer to environment.
|
||||
browser:
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
github.com:
|
||||
users:
|
||||
privilegedescalation-engineer[bot]:
|
||||
oauth_token: ghs_n2DXnoj38RccFYNlzH18XQ739bhr8e2w4BZK
|
||||
privilegedescalation-ceo[bot]:
|
||||
oauth_token: ghs_K7fsAgb8nVATb7zFV5VoZLUaRExyOX3uPkn3
|
||||
privilegedescalation-cto[bot]:
|
||||
oauth_token: ghs_OK6yqSB45aMkas1g5zgJKEgh2CoVH42JLuwu
|
||||
privilegedescalation-qa[bot]:
|
||||
oauth_token: ghs_ppIO9dekMz5A5uAqCPERzj5bk9jBHU2Bf0sL
|
||||
user: privilegedescalation-engineer[bot]
|
||||
oauth_token: ghs_n2DXnoj38RccFYNlzH18XQ739bhr8e2w4BZK
|
||||
@@ -1,440 +0,0 @@
|
||||
name: Plugin Release
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version (e.g. 1.0.0)'
|
||||
required: true
|
||||
type: string
|
||||
node-version:
|
||||
description: 'Node.js version to use'
|
||||
required: false
|
||||
type: string
|
||||
default: '22'
|
||||
upstream-repo:
|
||||
description: 'Upstream repo to fetch appVersion from (e.g. fenio/tns-csi). Leave empty to skip.'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
secrets:
|
||||
RELEASE_APP_ID:
|
||||
description: 'GitHub App ID for creating PRs (org blocks GITHUB_TOKEN from creating PRs)'
|
||||
required: true
|
||||
RELEASE_APP_PRIVATE_KEY:
|
||||
description: 'GitHub App private key (PEM format)'
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
check-secrets:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
ready: ${{ steps.check.outputs.ready }}
|
||||
steps:
|
||||
- name: Verify RELEASE_APP_ID is configured
|
||||
id: check
|
||||
env:
|
||||
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
||||
run: |
|
||||
if [ -z "$RELEASE_APP_ID" ]; then
|
||||
echo "::notice::RELEASE_APP_ID org secret is not configured (see PRI-380). Release skipped — no artifacts will be created."
|
||||
echo "ready=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "ready=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
ci:
|
||||
needs: check-secrets
|
||||
if: needs.check-secrets.outputs.ready == 'true'
|
||||
uses: ./.github/workflows/plugin-ci.yaml
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
|
||||
check-token-permissions:
|
||||
needs: check-secrets
|
||||
if: needs.check-secrets.outputs.ready == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
has_write: ${{ steps.check.outputs.has_write }}
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.RELEASE_APP_ID }}
|
||||
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Check write permissions via API
|
||||
id: check
|
||||
run: |
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
-X POST \
|
||||
-H "Authorization: Bearer ${{ steps.app-token.outputs.token }}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/git/refs" \
|
||||
-d '{"ref":"refs/heads/_release_check","sha":"${{ github.sha }}"}')
|
||||
if [ "$HTTP_CODE" = "201" ]; then
|
||||
echo "::notice::Token has write permission — cleaning up test ref."
|
||||
curl -s -o /dev/null -w "%{http_code}" \
|
||||
-X DELETE \
|
||||
-H "Authorization: Bearer ${{ steps.app-token.outputs.token }}" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/git/refs/heads/_release_check"
|
||||
echo "has_write=true" >> $GITHUB_OUTPUT
|
||||
elif [ "$HTTP_CODE" = "403" ]; then
|
||||
echo "::error::Token lacks write permission. Release cannot push tags or branches."
|
||||
echo "has_write=false" >> $GITHUB_OUTPUT
|
||||
exit 1
|
||||
else
|
||||
echo "::warning::Unexpected response ($HTTP_CODE) when checking write permission."
|
||||
echo "has_write=false" >> $GITHUB_OUTPUT
|
||||
exit 1
|
||||
fi
|
||||
|
||||
check-tag:
|
||||
needs: check-secrets
|
||||
if: needs.check-secrets.outputs.ready == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
skip: ${{ steps.check.outputs.skip }}
|
||||
steps:
|
||||
- name: Check if tag already exists
|
||||
id: check
|
||||
run: |
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
-H "Authorization: Bearer ${{ github.token }}" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/git/refs/tags/v${{ inputs.version }}")
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo "::notice::Tag v${{ inputs.version }} already exists. Release skipped (not an error)."
|
||||
echo "skip=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "skip=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
release:
|
||||
needs: [ci, check-tag, check-secrets, check-token-permissions]
|
||||
if: needs.check-secrets.outputs.ready == 'true' && needs.check-tag.outputs.skip != 'true' && needs.check-token-permissions.outputs.has_write == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
steps:
|
||||
- name: Validate version format
|
||||
run: |
|
||||
if [[ ! "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: Version must be in X.Y.Z format"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Detect package manager
|
||||
id: pkg-manager
|
||||
run: |
|
||||
if [ -f "pnpm-lock.yaml" ]; then
|
||||
echo "manager=pnpm" >> $GITHUB_OUTPUT
|
||||
echo "lockfile=pnpm-lock.yaml" >> $GITHUB_OUTPUT
|
||||
# Check for packageManager field in package.json (Corepack pinning).
|
||||
# pnpm/action-setup@v5 errors when packageManager is absent and no version
|
||||
# is specified, so use Corepack for repos that have the field pinned and
|
||||
# fall back to pnpm/action-setup with version: latest for repos that don't.
|
||||
PM=$(python3 -c "import json,sys; d=json.load(open('package.json')); print('true' if d.get('packageManager','').startswith('pnpm@') else 'false')" 2>/dev/null || echo "false")
|
||||
echo "has_package_manager=$PM" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "manager=npm" >> $GITHUB_OUTPUT
|
||||
echo "lockfile=package-lock.json" >> $GITHUB_OUTPUT
|
||||
echo "has_package_manager=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
# Only enable built-in npm caching here; pnpm caching is handled below
|
||||
# after pnpm is installed (corepack is not available before setup-node).
|
||||
cache: ${{ steps.pkg-manager.outputs.manager == 'npm' && 'npm' || '' }}
|
||||
|
||||
- name: Setup pnpm (via Corepack, reads version from packageManager field)
|
||||
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'true'
|
||||
run: |
|
||||
npm install -g corepack
|
||||
corepack enable pnpm
|
||||
corepack install
|
||||
|
||||
- name: Setup pnpm (version latest)
|
||||
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'false'
|
||||
uses: pnpm/action-setup@v5
|
||||
with:
|
||||
run_install: false
|
||||
version: latest
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-store
|
||||
if: steps.pkg-manager.outputs.manager == 'pnpm'
|
||||
run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pnpm store
|
||||
if: steps.pkg-manager.outputs.manager == 'pnpm'
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.pnpm-store.outputs.dir }}
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: Update version in package.json
|
||||
run: |
|
||||
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
|
||||
pnpm version ${{ inputs.version }} --no-git-tag-version --allow-same-version
|
||||
else
|
||||
npm version ${{ inputs.version }} --no-git-tag-version --allow-same-version
|
||||
fi
|
||||
|
||||
- name: Update artifacthub-pkg.yml
|
||||
run: |
|
||||
VERSION="${{ inputs.version }}"
|
||||
if [ -f artifacthub-pkg.yml ]; then
|
||||
PKG_NAME=$(grep '^name:' artifacthub-pkg.yml | cut -d: -f2 | tr -d ' "')
|
||||
else
|
||||
PKG_NAME=$(jq -r .name package.json | sed 's|^@[^/]*/||')
|
||||
fi
|
||||
RELEASE_URL="https://github.com/${{ github.repository }}/releases/download/v${VERSION}/${PKG_NAME}-${VERSION}.tar.gz"
|
||||
sed -i "s/^version:.*/version: \"${VERSION}\"/" artifacthub-pkg.yml
|
||||
sed -i "s|headlamp/plugin/archive-url:.*|headlamp/plugin/archive-url: \"${RELEASE_URL}\"|" artifacthub-pkg.yml
|
||||
|
||||
- name: Update appVersion from upstream release
|
||||
if: inputs.upstream-repo != ''
|
||||
run: |
|
||||
APP_VERSION=$(curl -sf "https://api.github.com/repos/${{ inputs.upstream-repo }}/releases/latest" | jq -r '.tag_name | ltrimstr("v")')
|
||||
if [ -z "$APP_VERSION" ] || [ "$APP_VERSION" = "null" ]; then
|
||||
echo "::warning::Could not fetch latest upstream release, skipping appVersion update"
|
||||
else
|
||||
sed -i "s|^appVersion:.*|appVersion: \"${APP_VERSION}\"|" artifacthub-pkg.yml
|
||||
echo "appVersion set to ${APP_VERSION}"
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
max_attempts=3
|
||||
attempt=1
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
echo "Attempt $attempt of $max_attempts"
|
||||
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
|
||||
pnpm install --frozen-lockfile && break
|
||||
else
|
||||
npm ci && break
|
||||
fi
|
||||
if [ $attempt -lt $max_attempts ]; then
|
||||
echo "::warning::Install step failed on attempt $attempt. Retrying in 5 seconds..."
|
||||
sleep 5
|
||||
fi
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
if [ $attempt -gt $max_attempts ]; then
|
||||
echo "::error::Install step failed after $max_attempts attempts."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build plugin
|
||||
run: npx @kinvolk/headlamp-plugin build
|
||||
|
||||
- name: Package plugin
|
||||
run: npx @kinvolk/headlamp-plugin package
|
||||
|
||||
- name: Prepare release tarball
|
||||
run: |
|
||||
VERSION="${{ inputs.version }}"
|
||||
# headlamp-plugin strips the @org/ prefix when naming tarballs.
|
||||
# e.g. @privilegedescalation/headlamp-argocd-plugin -> headlamp-argocd-plugin
|
||||
if [ -f artifacthub-pkg.yml ]; then
|
||||
PKG_NAME=$(grep '^name:' artifacthub-pkg.yml | cut -d: -f2 | tr -d ' "')
|
||||
else
|
||||
PKG_NAME=$(jq -r .name package.json | sed 's|^@[^/]*/||')
|
||||
fi
|
||||
TARBALL="${PKG_NAME}-${VERSION}.tar.gz"
|
||||
for f in *.tar.gz; do
|
||||
[ "$f" != "$TARBALL" ] && mv "$f" "$TARBALL"
|
||||
done
|
||||
if [ ! -f "$TARBALL" ]; then
|
||||
echo "Error: Expected tarball $TARBALL not found"
|
||||
ls -la *.tar.gz 2>/dev/null || echo "No .tar.gz files found"
|
||||
exit 1
|
||||
fi
|
||||
echo "TARBALL=$TARBALL" >> $GITHUB_ENV
|
||||
echo "PKG_NAME=$PKG_NAME" >> $GITHUB_ENV
|
||||
|
||||
- name: Validate tarball
|
||||
run: |
|
||||
echo "Tarball: ${{ env.TARBALL }}"
|
||||
ls -lh "${{ env.TARBALL }}"
|
||||
tar -tzf "${{ env.TARBALL }}" | head -20
|
||||
tar -tzf "${{ env.TARBALL }}" | grep -q "main.js" || { echo "Error: main.js not found in tarball"; exit 1; }
|
||||
|
||||
- name: Compute checksum
|
||||
run: |
|
||||
CHECKSUM=$(sha256sum "${{ env.TARBALL }}" | awk '{print $1}')
|
||||
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV
|
||||
sed -i "s|headlamp/plugin/archive-checksum:.*|headlamp/plugin/archive-checksum: sha256:${CHECKSUM}|" artifacthub-pkg.yml
|
||||
|
||||
- name: Commit and tag
|
||||
run: |
|
||||
VERSION="${{ inputs.version }}"
|
||||
BRANCH="release/v${VERSION}"
|
||||
# If the release branch already exists (e.g. from a failed prior run),
|
||||
# delete it so the re-trigger can proceed cleanly. The check-tag job
|
||||
# above already skips when the tag exists, so we only reach here when
|
||||
# the tag does NOT exist yet — safe to remove a stale branch.
|
||||
if git ls-remote --exit-code origin "refs/heads/$BRANCH" 2>/dev/null; then
|
||||
echo "::notice::Branch $BRANCH already exists — deleting for clean re-trigger."
|
||||
git push origin --delete "$BRANCH"
|
||||
fi
|
||||
git checkout -b "$BRANCH"
|
||||
git add package.json "${{ steps.pkg-manager.outputs.lockfile }}" artifacthub-pkg.yml
|
||||
git commit -m "release: v${VERSION}"
|
||||
git tag "v${VERSION}"
|
||||
git push origin "$BRANCH"
|
||||
git push origin "refs/tags/v${VERSION}"
|
||||
|
||||
- name: Generate GitHub App token
|
||||
id: app-token
|
||||
uses: actions/create-github-app-token@v3
|
||||
with:
|
||||
app-id: ${{ secrets.RELEASE_APP_ID }}
|
||||
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: "v${{ inputs.version }}"
|
||||
files: ${{ env.TARBALL }}
|
||||
fail_on_unmatched_files: false
|
||||
generate_release_notes: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Install GitHub CLI
|
||||
run: |
|
||||
if ! command -v gh &>/dev/null; then
|
||||
GH_VERSION="2.74.0"
|
||||
curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" -o /tmp/gh.tar.gz
|
||||
tar -xzf /tmp/gh.tar.gz -C /tmp
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
mv "/tmp/gh_${GH_VERSION}_linux_amd64/bin/gh" "$HOME/.local/bin/gh"
|
||||
rm -rf /tmp/gh.tar.gz "/tmp/gh_${GH_VERSION}_linux_amd64"
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
"$HOME/.local/bin/gh" --version
|
||||
fi
|
||||
|
||||
- name: Create PR for version bump
|
||||
run: |
|
||||
set -o pipefail
|
||||
VERSION="${{ inputs.version }}"
|
||||
BODY=$(printf "Automated version bump and checksum update for v%s.\n\ncc @cpfarhood" "${VERSION}")
|
||||
# Create PR only if an OPEN one doesn't already exist.
|
||||
# Note: gh pr view also finds MERGED PRs; we must check for open ones explicitly
|
||||
# so that a re-trigger after a stale-branch delete creates a fresh PR.
|
||||
OPEN_PR=$(gh pr list --base main --head "release/v${VERSION}" --state open --json number --jq '.[0].number' 2>/dev/null)
|
||||
if [ -z "$OPEN_PR" ]; then
|
||||
gh pr create \
|
||||
--title "release: v${VERSION}" \
|
||||
--body "$BODY" \
|
||||
--base main \
|
||||
--head "release/v${VERSION}"
|
||||
# Pull the number again to handle both create and pre-existing cases
|
||||
OPEN_PR=$(gh pr list --base main --head "release/v${VERSION}" --state open --json number --jq '.[0].number' 2>/dev/null)
|
||||
else
|
||||
echo "::notice::Open PR #${OPEN_PR} for release/v${VERSION} already exists — skipping creation."
|
||||
fi
|
||||
# Guard: ensure we have a PR number before proceeding
|
||||
if [ -z "$OPEN_PR" ]; then
|
||||
echo "::error::Could not determine PR number for release/v${VERSION}."
|
||||
exit 1
|
||||
fi
|
||||
echo "::notice::Working with PR #${OPEN_PR}"
|
||||
|
||||
# Check if PR was already merged (idempotency — safe to re-trigger after a stale branch)
|
||||
MERGED_CHECK=$(gh pr view "$OPEN_PR" --json state --jq '.state' 2>/dev/null)
|
||||
if [ "$MERGED_CHECK" = "MERGED" ]; then
|
||||
echo "::notice::PR #${OPEN_PR} was already merged. Nothing to do."
|
||||
exit 0
|
||||
fi
|
||||
# Determine whether to use --auto or not based on current status.
|
||||
# Retry the status check up to 3 times with exponential back-off when
|
||||
# GitHub is still computing the merge state (UNKNOWN state).
|
||||
MAX_RETRIES=3
|
||||
BACKOFF=3
|
||||
MERGE_STATE=""
|
||||
for i in $(seq 1 $MAX_RETRIES); do
|
||||
MERGE_STATE=$(gh pr view "$OPEN_PR" --json mergeStateStatus --jq '.mergeStateStatus' 2>/dev/null)
|
||||
if [ "$MERGE_STATE" != "UNKNOWN" ]; then
|
||||
break
|
||||
fi
|
||||
if [ $i -lt $MAX_RETRIES ]; then
|
||||
echo "PR merge state is UNKNOWN (GitHub still computing). Retry ${i}/${MAX_RETRIES} in ${BACKOFF}s..."
|
||||
sleep $BACKOFF
|
||||
BACKOFF=$((BACKOFF * 2))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$MERGE_STATE" = "BLOCKED" ] || [ "$MERGE_STATE" = "UNKNOWN" ]; then
|
||||
echo "PR is $MERGE_STATE — attempting auto-merge (safe fallback, waits for branch protection checks)."
|
||||
if gh pr merge "$OPEN_PR" --auto --squash --delete-branch 2>&1; then
|
||||
echo "Auto-merge initiated successfully."
|
||||
else
|
||||
AUTO_MERGE_ERR=$?
|
||||
# If --auto failed because auto-merge is disabled for this repo
|
||||
# (autoMergeAllowed: false), fall back to --admin which merges
|
||||
# regardless of branch protection rules. --admin requires GitHub
|
||||
# App token, not GITHUB_TOKEN, so GH_TOKEN is already correct.
|
||||
if gh pr merge "$OPEN_PR" --admin --squash --delete-branch 2>&1; then
|
||||
echo "Auto-merge unavailable (autoMergeAllowed: false) — merged via --admin."
|
||||
else
|
||||
echo "::error::Both --auto and --admin merge failed. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "PR is $MERGE_STATE — merging directly."
|
||||
gh pr merge "$OPEN_PR" --squash --delete-branch
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Verify checksums are consistent (main == tag == tarball)
|
||||
run: |
|
||||
VERSION="${{ inputs.version }}"
|
||||
TARBALL_CS=$(sha256sum "${{ env.TARBALL }}" | awk '{print $1}')
|
||||
|
||||
# Checksum recorded in the tag's artifacthub-pkg.yml
|
||||
TAG_CS=$(git show "v${VERSION}:artifacthub-pkg.yml" 2>/dev/null | grep "archive-checksum" | awk '{print $2}' | sed 's/sha256://')
|
||||
|
||||
# Checksum now on main (after PR merge)
|
||||
MAIN_CS=$(git fetch origin main 2>/dev/null; git show "origin/main:artifacthub-pkg.yml" | grep "archive-checksum" | awk '{print $2}' | sed 's/sha256://')
|
||||
|
||||
echo "Tarball SHA256 : $TARBALL_CS"
|
||||
echo "Tag artifacthub: $TAG_CS"
|
||||
echo "Main artifacthub: $MAIN_CS"
|
||||
|
||||
FAIL=0
|
||||
[ "$TARBALL_CS" != "$TAG_CS" ] && echo "ERROR: tag checksum mismatch!" && FAIL=1
|
||||
[ "$TARBALL_CS" != "$MAIN_CS" ] && echo "ERROR: main checksum mismatch!" && FAIL=1
|
||||
[ "$FAIL" = "1" ] && exit 1
|
||||
echo "All checksums consistent — ArtifactHub will index correctly."
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Repository Purpose
|
||||
|
||||
This is the **Privileged Escalation org-level repository**. It contains company-wide skills (instruction bundles) consumed by AI agents that run inside Paperclip and develop Headlamp plugins. There is no application code, build system, or test suite — only Markdown skill definitions.
|
||||
|
||||
## Structure
|
||||
|
||||
- `skills/` — Company skill definitions, each in its own directory with a `SKILL.md` file
|
||||
- `skills/safety/SKILL.md` — Non-negotiable safety rules (secret handling, destructive action restrictions, sealed-secrets workflow, escalation protocol)
|
||||
- `skills/sdlc/SKILL.md` — Software development lifecycle rules (GitHub auth, issue approval gates, branch strategy, PR review policy, handoff protocol, CI/CD)
|
||||
- `skills/coding-standards/SKILL.md` — Headlamp plugin development conventions (stack, commands, registration API, shared libraries)
|
||||
|
||||
## Skill File Format
|
||||
|
||||
Each skill is a Markdown file with YAML frontmatter containing `name` and `description` fields:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: skill-name
|
||||
description: >
|
||||
One-line description of what the skill covers.
|
||||
---
|
||||
|
||||
# Skill Title
|
||||
|
||||
Content...
|
||||
```
|
||||
|
||||
## Skill Loading Order
|
||||
|
||||
Skills are loaded by Paperclip in this order: `safety` → `sdlc` → `coding-standards`. Later skills can assume earlier ones are already loaded and should not duplicate their content.
|
||||
Submodule
+1
Submodule headlamp-rook-plugin added at 79eaa6910d
Submodule
+1
Submodule headlamp-sealed-secrets-plugin added at 143b2c36e0
Binary file not shown.
|
Before Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1 @@
|
||||
test
|
||||
Submodule
+1
Submodule org added at c420e1543f
@@ -1,62 +1,54 @@
|
||||
---
|
||||
name: coding-standards
|
||||
description: >
|
||||
Engineering quality bar for GroomBook code: priority ordering of correctness
|
||||
vs. clarity vs. maintainability vs. performance vs. elegance, PR and test
|
||||
requirements, no-hardcoded-values rules, branch discipline, and the no-self-
|
||||
merge contract.
|
||||
Coding standards for Privileged Escalation. Covers Headlamp plugin
|
||||
development workflow, registration API, and shared libraries.
|
||||
---
|
||||
|
||||
# Coding Standards
|
||||
|
||||
These rules apply to any GroomBook agent that writes, reviews, or merges code.
|
||||
## Headlamp Plugins
|
||||
|
||||
## Priority ordering
|
||||
All plugins extend [Headlamp](https://headlamp.dev/docs/latest/development/plugins/getting-started), a Kubernetes dashboard with a plugin system.
|
||||
|
||||
When making technical decisions, prioritize in this order:
|
||||
- **Language:** TypeScript + React 18, MUI v5
|
||||
- **Scaffolding:** `npx --yes @kinvolk/headlamp-plugin create <plugin-name>`
|
||||
- **Entry point:** `src/index.tsx`
|
||||
- **Linting:** ESLint via `@headlamp-k8s/eslint-config` + Prettier
|
||||
- **Testing:** Vitest + React Testing Library
|
||||
|
||||
1. **Correctness** — does it work? Does it handle edge cases? Have you proven it, not assumed it?
|
||||
2. **Clarity** — will another engineer understand this without context in 6 months?
|
||||
3. **Maintainability** — will it be safe to change?
|
||||
4. **Performance** — fast enough for the use case? Profile before optimizing.
|
||||
5. **Elegance** — nice if free; never trade any of the above for it.
|
||||
### Plugin Commands
|
||||
|
||||
## Pull request discipline
|
||||
Run from the plugin directory:
|
||||
|
||||
* All changes go through a PR. **Never push directly to `dev`, `uat`, or `main`.**
|
||||
* No agent merges their own PR.
|
||||
* Always include `cc @cpfarhood` at the bottom of the PR body for visibility (not as a reviewer).
|
||||
| Command | Purpose |
|
||||
|---|---|
|
||||
| `npm run start` | Dev mode with hot reload |
|
||||
| `npm run build` | Production build (`dist/main.js`) |
|
||||
| `npm run format` | Prettier format |
|
||||
| `npm run lint` | ESLint check |
|
||||
| `npm run lint-fix` | ESLint auto-fix |
|
||||
| `npm run tsc` | Typecheck |
|
||||
| `npm run test` | Vitest tests |
|
||||
|
||||
## Test requirements
|
||||
### Registration API
|
||||
|
||||
* **Every PR must include tests** for new code paths. No exceptions for "small" changes.
|
||||
* Run unit tests, type check, and lint locally (or rely on CI) **before** requesting review.
|
||||
* A PR without passing tests does not get approval.
|
||||
* New code paths require coverage. No coverage = no approval.
|
||||
Import from `@kinvolk/headlamp-plugin/lib`:
|
||||
|
||||
## Code review tone
|
||||
- `registerAppBarAction()` — add components to the nav bar
|
||||
- `registerRoute()` — create new pages
|
||||
- `registerSidebarEntry()` — add sidebar items
|
||||
- `registerDetailsViewSection()` — extend resource detail views
|
||||
- `registerPluginSettings()` — add plugin configuration UI
|
||||
|
||||
Hold a high bar. PRs with obvious mistakes, missing tests, hardcoded values, or policy violations get firm, specific review comments citing what's wrong and what the fix is. Cite the file and line. Suggest the fix when you know it. Don't sugarcoat — but be professional and constructive. "This looks wrong" is not a review comment.
|
||||
### K8s API Access
|
||||
|
||||
## Hardcoded values
|
||||
```typescript
|
||||
import { K8s } from '@kinvolk/headlamp-plugin/lib';
|
||||
const [pods, error] = K8s.ResourceClasses.Pod.useList();
|
||||
```
|
||||
|
||||
* **Colors** use CSS variables / theme tokens. Never raw hex in components.
|
||||
* **Strings** use constants or i18n. No magic strings.
|
||||
* **Numbers** that aren't trivially obvious go in named constants.
|
||||
* **No magic numbers** in business logic.
|
||||
### Shared Libraries
|
||||
|
||||
## Secrets in code
|
||||
|
||||
Secrets never touch source. See the `safety` skill for the SealedSecrets workflow. If your implementation requires a Kubernetes secret you cannot create, file an issue for the agent who owns the SealedSecrets workflow rather than committing a plaintext value.
|
||||
|
||||
## Releases and versioning
|
||||
|
||||
All releases use CalVer (`YYYY.MMDD.PATCH`, e.g. `2026.0504.0`). No SemVer, no custom schemes.
|
||||
|
||||
## Container images
|
||||
|
||||
Push to `ghcr.io` only. Never Docker Hub for first-party images.
|
||||
|
||||
## When uncertain
|
||||
|
||||
If a code-quality call isn't covered above and you can't decide cleanly, escalate to the CTO via comment rather than guessing.
|
||||
These are provided by Headlamp at runtime — **do not bundle them**:
|
||||
React, React Router, Redux, MUI, Lodash, Monaco Editor, Notistack, Iconify.
|
||||
|
||||
+12
-17
@@ -1,31 +1,26 @@
|
||||
---
|
||||
name: safety
|
||||
description: >
|
||||
Non-negotiable safety rules for all GroomBook agents. Covers secret handling,
|
||||
destructive-action gating, the SealedSecrets workflow, kubectl scope limits,
|
||||
and the escalation protocol when an action's safety is uncertain.
|
||||
Non-negotiable safety rules for all agents at Privileged Escalation. Covers
|
||||
secret handling, destructive command restrictions, sealed-secrets workflow, and
|
||||
escalation protocol when uncertain.
|
||||
---
|
||||
|
||||
# Safety
|
||||
# Safety Considerations
|
||||
|
||||
The following rules apply to every GroomBook agent without exception.
|
||||
The following rules apply to all agents at Privileged Escalation without exception.
|
||||
|
||||
## Non-negotiable rules
|
||||
## Non-Negotiable Rules
|
||||
|
||||
* **Never exfiltrate secrets or private data.** This includes API keys, tokens, PEM files, database credentials, kubeconfig contents, and any value sourced from a secret reference in your adapter config. Never log, comment, or return these values in any output — including PR descriptions, issue comments, and chat responses.
|
||||
* **Never exfiltrate secrets or private data.** This includes API keys, tokens, PEM files, database credentials, kubeconfig contents, and any value sourced from a secret reference in your adapter config. Do not log, comment, or return these values in any output.
|
||||
|
||||
* **Seek board approval before destructive actions.** "Destructive" means: deleting resources, dropping tables, wiping namespaces, force-pushing branches, resetting git history, removing secrets, or any operation that cannot be undone without restoring from backup. Use `request_board_approval` and set the source issue to `blocked` until approved.
|
||||
* **Seek Board Approval for Destructive Actions.** Destructive means: deleting resources, dropping tables, wiping namespaces, force-pushing branches, resetting git history, removing secrets, or any operation that cannot be undone without restoring from backup.
|
||||
|
||||
* **Never commit plaintext secrets.** Kubernetes secrets go through Bitnami Sealed Secrets (`kubeseal`). Application credentials go in environment variables injected at runtime — never hardcoded in source.
|
||||
* **No plaintext secrets in any repository.** Kubernetes secrets go through Bitnami Sealed Secrets (`kubeseal`). Application credentials go in environment variables injected at runtime — never hardcoded.
|
||||
|
||||
* **Never `kubectl apply` against production (`groombook`).** The production namespace is Flux-managed. Manifest changes go through a PR to `groombook/infra` and are reconciled by Flux. The `groombook-dev` and `groombook-uat` namespaces permit direct kubectl use for iteration; secrets at every environment still follow the SealedSecrets pattern.
|
||||
|
||||
* **Never `kubectl create secret` in production.** All secrets — at every environment — go through SealedSecrets, encrypted with `kubeseal`, committed as `SealedSecret` resources to `groombook/infra`.
|
||||
|
||||
* **Never bypass the merge gate.** No self-merging PRs. No pushing directly to `dev`, `uat`, or `main`. Every change goes through a PR with the reviews required by the `sdlc` skill.
|
||||
|
||||
* **Never run `tofu` directly.** Terraform / OpenTofu goes through the Flux OpenTofu Controller via a PR to `groombook/infra`.
|
||||
* **Do not use `kubectl create` in production.**
|
||||
The `privilegedescalation` namespace is Flux-managed. Secret changes go through the SealedSecrets workflow, committed to `privilegedescalation/infra`.
|
||||
|
||||
## If you are unsure
|
||||
|
||||
If you are unsure whether an action is safe, **stop**. Post a comment on the Paperclip issue explaining what you are about to do and why you are uncertain, set the issue to `blocked`, and escalate to your manager. Do not guess.
|
||||
If you are unsure whether an action is safe, stop. Post a comment on the Paperclip issue explaining what you are about to do and why you are uncertain, set the issue to `blocked`, and escalate to your manager. Do not guess.
|
||||
|
||||
+157
-144
@@ -1,229 +1,242 @@
|
||||
---
|
||||
name: sdlc
|
||||
description: >
|
||||
Software development lifecycle for GroomBook. Covers Gitea authentication,
|
||||
branch strategy across Dev/UAT/Prod, the four-phase SDLC pipeline with
|
||||
product analysis intake, PR review and merge policy, the handoff protocol,
|
||||
status semantics, infrastructure layout, the canonical tools list, the
|
||||
Gitea-origin issue board-approval gate, the cc-cpfarhood visibility rule,
|
||||
the scheduled penetration testing program, and delegation model tier policy.
|
||||
Software development lifecycle rules for Privileged Escalation. Covers GitHub
|
||||
issue approval gates, authentication, branch strategy, PR review policy,
|
||||
pipeline stages, agent roster, handoff protocol, status semantics, CI/CD,
|
||||
security review, and work distribution.
|
||||
---
|
||||
|
||||
# Software Development Lifecycle
|
||||
|
||||
## Gitea authentication
|
||||
## GitHub Authentication
|
||||
|
||||
**Use the `tea` CLI** with the `GITEA_TOKEN` environment variable for all Gitea operations. Configure it once:
|
||||
**Invoke the `github-app-token` skill** before any GitHub operation. It generates a short-lived installation token and sets `GH_TOKEN`. **Never** run `gh auth login` directly — it hangs headless agents.
|
||||
|
||||
```bash
|
||||
tea login add --url https://git.farh.net --token $GITEA_TOKEN --name groombook
|
||||
```
|
||||
Token expires after ~1 hour. Re-invoke the skill to regenerate if needed.
|
||||
|
||||
Gitea is the **primary source of truth**. Every Paperclip issue should have a corresponding Gitea issue (create one if missing). Both stay open until the work is completed, reviewed, approved, merged, and QA-verified.
|
||||
## GitHub Issues — Board Approval Required
|
||||
|
||||
## Gitea-origin issue policy — board approval required
|
||||
|
||||
If a task originated from Gitea (`originKind: "gitea"`), **do not begin work**. Immediately create a board approval:
|
||||
**If a task originated from GitHub (`originKind: "github"` in the issue data), do not begin any work.** Immediately create a `request_board_approval`:
|
||||
|
||||
```
|
||||
POST /api/companies/{companyId}/approvals
|
||||
{
|
||||
"type": "request_board_approval",
|
||||
"requestedByAgentId": "{your-agent-id}",
|
||||
"issueIds": ["{issueId}"],
|
||||
"issueIds": ["{issue-id}"],
|
||||
"payload": {
|
||||
"title": "Board approval required: Gitea issue",
|
||||
"summary": "Summarize what the Gitea issue requests.",
|
||||
"title": "Board approval required: GitHub issue",
|
||||
"summary": "Summarize what the GitHub issue requests.",
|
||||
"recommendedAction": "Approve to begin work.",
|
||||
"risks": ["Work begins without board review if approved."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Set the issue to `blocked` with a comment linking to the approval. Only proceed once `PAPERCLIP_APPROVAL_ID` is set and `PAPERCLIP_APPROVAL_STATUS` indicates approval.
|
||||
Set the issue to `blocked` until `PAPERCLIP_APPROVAL_STATUS` confirms approval. Only proceed once approved.
|
||||
|
||||
## Branch strategy
|
||||
## Branch Strategy
|
||||
|
||||
Three long-lived branches map to the three deployment environments:
|
||||
All plugin repositories use a single long-lived branch:
|
||||
|
||||
| Branch | Environment | Who merges |
|
||||
|--------|-------------|-----------|
|
||||
| `dev` | Dev | CTO (after QA approval) |
|
||||
| `uat` | UAT | CTO (promotes `dev` → `uat`) |
|
||||
| `main` | Production | CEO (promotes `uat` → `main`) |
|
||||
|--------|-------------|------------|
|
||||
| `main` | Production | CEO (Countess von Containerheim) after triple approval |
|
||||
|
||||
**Engineers always target `dev`** — never `uat` or `main` directly. Feature branches: `<agent-name>/<short-description>`.
|
||||
**Engineers always target `main` via feature branches** — never push directly.
|
||||
|
||||
## Pull requests
|
||||
Feature branches follow the convention: `<agent-name>/<short-description>` (e.g., `gandalf/add-sealed-secrets-list`).
|
||||
|
||||
All changes happen via pull request. Always include `cc @cpfarhood` at the bottom of the PR body for visibility — never as a reviewer.
|
||||
## Pull Requests
|
||||
|
||||
All changes must happen via pull request. Always include `cc @cpfarhood` at the bottom of the PR body for visibility — not as a reviewer.
|
||||
|
||||
```bash
|
||||
tea pr create --base dev --title "..." --body "... cc @cpfarhood"
|
||||
gh pr create --title "..." --body "... cc @cpfarhood"
|
||||
```
|
||||
|
||||
## PR review & merge policy
|
||||
## PR Review & Merge Policy
|
||||
|
||||
### Dev branch (`dev`)
|
||||
**Do not approve a PR with failing tests, type errors, or no coverage for new code.**
|
||||
|
||||
- **QA** (Lint Roller) reviews the PR. Approve → hand to CTO. Fail → back to engineer directly with exact details.
|
||||
- **CTO** (The Dogfather) reviews. Approve → CTO merges the `dev` PR. Fail → back to engineer.
|
||||
Requires **3 approving GitHub reviews** before the CEO merges:
|
||||
|
||||
### UAT branch (`uat`)
|
||||
1. **UAT (Pixel Patty)** — E2E browser testing against `headlamp-dev`
|
||||
2. **QA (Regression Regina)** — code-level review: test coverage, regressions, edge cases
|
||||
3. **CTO (Null Pointer Nancy)** — architecture alignment, code quality, security
|
||||
|
||||
- **CTO** opens and merges a `dev` → `uat` PR.
|
||||
**Review order is mandatory: CI → UAT → QA → CTO → CEO merge.** Each stage gates the next. No agent merges their own PRs.
|
||||
|
||||
### Main branch (`main`)
|
||||
## 48-Hour PR Review SLA (Binding)
|
||||
|
||||
- **CEO** (Scrubs McBarkley) reviews and merges the `uat` → `main` PR.
|
||||
**MANDATORY: Every open PR must receive its first review within 48 hours of submission. No exceptions.**
|
||||
|
||||
`@cpfarhood` is cc'd for visibility on all PRs — never as a reviewer.
|
||||
### SLA Assignments & Responsibility
|
||||
- **0-24 hours:** Assigned reviewer must begin review (or explicitly hand off)
|
||||
- **24-48 hours:** Assigned reviewer must complete review or be flagged for SLA violation
|
||||
- **48+ hours:** SLA violation is documented and escalated
|
||||
|
||||
## SDLC pipeline
|
||||
### Assigned Reviewers & Accountability
|
||||
1. **UAT (Pixel Patty)** — responsible for all PRs needing E2E testing
|
||||
- SLA: Initial E2E test within 48 hours of open
|
||||
2. **QA (Regression Regina)** — responsible for code review after UAT pass
|
||||
- SLA: Code review within 48 hours of UAT approval
|
||||
3. **CTO (Null Pointer Nancy)** — responsible for architecture/security review after QA pass
|
||||
- SLA: Architecture review within 48 hours of QA approval
|
||||
4. **CEO (Countess von Containerheim)** — responsible for SLA enforcement
|
||||
- Enforces SLA via daily audit and escalation
|
||||
|
||||
### Phase 0 — Product analysis (feature intake)
|
||||
### Escalation Protocol (CEO Responsibility)
|
||||
- **At 24 hours:** CEO tags reviewer with automated comment and surfaces PR in daily status
|
||||
- **At 48 hours:** CEO blocks PR from merge queue; escalates to reviewer's manager (CTO for most)
|
||||
- **At 72+ hours:** If critical-path, PR blocks next release until review completes or reviewer hands off
|
||||
|
||||
* Feature requests arrive at the CEO via Paperclip or Gitea Issues.
|
||||
* CEO delegates to CMPO (Pawla Abdul) for review.
|
||||
* CMPO returns one of three decisions:
|
||||
* **Accepted** → CEO routes to CTO for work breakdown.
|
||||
* **Backlogged** → CEO handles prioritization.
|
||||
* **Denied** → CEO closes as unplanned.
|
||||
* CTO breaks accepted work into atomic tasks and assigns to Engineering.
|
||||
### Exception Policy
|
||||
If a reviewer cannot meet SLA:
|
||||
- They must explicitly hand off to another reviewer within the 48-hour window
|
||||
- If hand-off doesn't happen, the SLA breach is documented and escalated
|
||||
- Rare exceptions require board approval (documented in PR)
|
||||
|
||||
### Phase 1 — Dev
|
||||
### Enforcement Mechanism
|
||||
CEO creates daily automated report of SLA status and escalates immediately when thresholds breach. This is non-negotiable work.
|
||||
|
||||
1. **Engineer** (Flea Flicker) branches from `dev`, writes code. GitOps deploys to dev on demand.
|
||||
2. **Engineer** opens a PR against `dev`. CI must pass.
|
||||
3. **QA (Lint Roller)** reviews the PR. Fail → back to engineer.
|
||||
4. QA approves and hands off to CTO.
|
||||
5. **CTO (The Dogfather)** reviews the PR. Fail → back to engineer.
|
||||
6. **CTO** merges the dev PR.
|
||||
7. **CI** builds and deploys automatically to Dev (`https://dev.groombook.dev`).
|
||||
## Pipeline
|
||||
|
||||
### Phase 2 — UAT promotion
|
||||
```
|
||||
CI: Engineer opens PR → CI runs (lint, types, unit tests)
|
||||
UAT: Pixel Patty validates E2E in headlamp-dev
|
||||
QA: Regression Regina reviews code quality and test coverage
|
||||
CTO: Null Pointer Nancy reviews architecture and security
|
||||
Merge: Countess von Containerheim merges after all approvals
|
||||
```
|
||||
|
||||
8. **CTO** opens and merges a PR from `dev` to `uat`.
|
||||
9. **CI** builds and deploys automatically to UAT (`https://uat.groombook.dev`).
|
||||
10. **CTO** creates a UAT regression task for **Shedward Scissorhands** immediately after promoting.
|
||||
### Stage 1 — Engineer Opens PR
|
||||
|
||||
### Phase 3 — UAT testing & security
|
||||
1. Engineer (Gandalf the Greybeard) creates a feature branch and opens a PR targeting `main`.
|
||||
2. CI runs automatically: lint, type checks, unit tests.
|
||||
3. CI must pass before any reviewer spends tokens. If CI fails, the engineer fixes it.
|
||||
|
||||
11. **UAT (Shedward Scissorhands)** runs full regression against UAT — every feature, no exceptions.
|
||||
12. UAT fail → CTO redistributes to engineer (return to Phase 1).
|
||||
13. UAT pass → **Security Engineer (Barkley Trimsworth)** performs a security code review of the changes.
|
||||
14. Security fail → CTO redistributes to engineer (return to Phase 1).
|
||||
### Stage 2 — UAT Review
|
||||
|
||||
### Phase 4 — Production
|
||||
4. Pixel Patty picks up PRs with passing CI.
|
||||
5. Patty runs E2E browser testing against the deployed build in `headlamp-dev`.
|
||||
6. Pass → hands off to QA. Fail → goes directly to engineer.
|
||||
|
||||
15. Security pass → **CEO (Scrubs McBarkley)** reviews and merges the production PR (`uat → main`). Fail → back to CTO.
|
||||
16. **CI** deploys automatically to Production (`https://demo.groombook.dev`).
|
||||
### Stage 3 — QA Review
|
||||
|
||||
### Hierarchy rules
|
||||
7. Regression Regina picks up PRs that have passed both CI and UAT.
|
||||
8. Regina reviews: test coverage, regressions, edge cases, code quality.
|
||||
9. Pass → hands off to CTO. Fail → goes directly to engineer.
|
||||
|
||||
* CTO rejections at Dev go directly to the engineer (not back through QA).
|
||||
* UAT failures (Shedward) go to CTO — CTO cascades to engineer.
|
||||
* Security failures (Barkley) go to CTO — CTO cascades to engineer.
|
||||
* CEO rejections at Prod go to CTO.
|
||||
### Stage 4 — CTO Review
|
||||
|
||||
> **Penetration testing.** Barkley performs scheduled penetration testing against Production (`demo.groombook.dev`) and Demo independently of the PR workflow. Board-authorized; not triggered per-PR. Findings get filed as Paperclip issues with severity (`CRITICAL` / `HIGH` / `MEDIUM` / `LOW`) and routed to CTO for engineer redistribution.
|
||||
10. Null Pointer Nancy picks up PRs that have passed CI, UAT, and QA.
|
||||
11. Nancy reviews: architecture alignment, code quality, security.
|
||||
12. Approve → PR is ready for merge. Request changes → goes directly to engineer.
|
||||
|
||||
## Delegation model tier
|
||||
### Stage 5 — CEO Merge
|
||||
|
||||
When creating subtasks for other agents, set `modelProfile: "cheap"` only for:
|
||||
- Mechanical refactors or repetitive operations
|
||||
- Basic information lookups
|
||||
- Well-specified, bounded updates
|
||||
13. Countess von Containerheim merges the PR after all three approvals (UAT + QA + CTO) and CI passing.
|
||||
14. Reject → returns to CTO → engineer.
|
||||
|
||||
Leave `modelProfile` unset for anything requiring judgment, reasoning, or QA review.
|
||||
### Pipeline Routing
|
||||
|
||||
When in doubt, leave it unset.
|
||||
PRs are classified into two pipelines based on changed files:
|
||||
|
||||
## Handoff protocol — mandatory
|
||||
| Pipeline | Trigger | Stages |
|
||||
|----------|---------|--------|
|
||||
| **A — User-facing** | PR changes any plugin runtime files (`src/`, `package.json`, `pnpm-lock.yaml`, `tsconfig.json`, `artifacthub-pkg.yml`) | CI → UAT → QA → CTO → CEO merge |
|
||||
| **B — Infrastructure** | PR changes ONLY infrastructure files (`.github/`, `*.md`, `.eslintrc*`, `.prettierrc*`, `renovate.json*`, `.gitignore`, `.editorconfig`, `LICENSE`) | CI → QA → CTO → CEO merge (skip UAT) |
|
||||
|
||||
**Default is Pipeline A** — if any changed file does not match the infrastructure-only list, the full pipeline applies.
|
||||
|
||||
Pipeline B requires **2 approving GitHub reviews** (QA + CTO) instead of 3. The `dual-approval-check` workflow enforces this gate.
|
||||
|
||||
### Hierarchy Rules
|
||||
|
||||
- CTO rejections go directly to engineer (not through QA or UAT).
|
||||
- UAT failures go directly to engineer (not through QA or UAT).
|
||||
- QA failures go directly to engineer (not through QA or UAT).
|
||||
- CEO rejections go to CTO, who cascades to engineer.
|
||||
- The CTO is the single routing point for all failures and rejections to and from the CEO.
|
||||
|
||||
## Agent Roster
|
||||
|
||||
| Role | Agent | Paperclip UUID |
|
||||
|------|-------|----------------|
|
||||
| CEO | Countess von Containerheim | `498f4d36-8e5b-4114-8514-d0698a091bd5` |
|
||||
| CTO | Null Pointer Nancy | `ed1eec37-f868-41b6-bc72-a3493bbce090` |
|
||||
| Staff Engineer | Gandalf the Greybeard | `fc07dd00-c4c2-4fa0-9a18-dd6fbb1d1eb4` |
|
||||
| QA Engineer | Regression Regina | `fd5dbec8-ddbb-4b57-9703-624e0ed90053` |
|
||||
| UAT Engineer | Pixel Patty | `01ec02f7-70c2-4fa1-ac3f-2545f1237ac3` |
|
||||
| VP Engineering Ops | Hugh Hackman | `2c97cff6-0f0b-4cff-967f-ca244eb2ef9b` |
|
||||
| CMO | Kubectl Karen | `95314e13-bea7-459d-a637-92381dede759` |
|
||||
|
||||
## Handoff Protocol — Mandatory
|
||||
|
||||
Every handoff to another agent requires ALL THREE steps:
|
||||
|
||||
### 1. Explicit assignment
|
||||
### Step 1 — Explicit Assignment
|
||||
|
||||
`PATCH /api/issues/{id}` with `assigneeAgentId: "<target-agent-uuid>"`. Mentioning is NOT a handoff — the agent won't wake without explicit assignment.
|
||||
PATCH the issue with `assigneeAgentId: "<target-agent-uuid>"`.
|
||||
@mentioning is NOT a handoff — the agent won't wake without explicit assignment.
|
||||
|
||||
### 2. Status = `todo`
|
||||
### Step 2 — Status = `todo`
|
||||
|
||||
Every handoff sets `status: "todo"`. Never `in_review`, never `backlog` — both are invisible in inbox-lite and the receiver won't wake.
|
||||
Every handoff sets `status: "todo"`. Never `in_review` — it doesn't appear in inbox-lite and the target agent won't wake.
|
||||
|
||||
### 3. Release checkout
|
||||
### Step 3 — Release Checkout
|
||||
|
||||
```
|
||||
POST /api/issues/{issueId}/release
|
||||
Headers: Authorization: Bearer $PAPERCLIP_API_KEY, X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID
|
||||
```
|
||||
|
||||
Without this release, the receiving agent cannot check out the issue.
|
||||
Without this release, the receiving agent cannot checkout the issue.
|
||||
|
||||
**Saying you are reassigning a task is NOT the same as reassigning it.** Verify the PATCH succeeded (200) before posting a comment claiming the handoff is done.
|
||||
## Status Semantics
|
||||
|
||||
## Infrastructure
|
||||
| Status | Meaning |
|
||||
|--------|---------|
|
||||
| `backlog` | Not ready; parked or unscheduled |
|
||||
| `todo` | Ready and actionable; not checked out |
|
||||
| `in_progress` | Actively owned; enter by checkout only |
|
||||
| `in_review` | Self-held only; awaiting external feedback |
|
||||
| `blocked` | Cannot proceed; state blocker and who must act |
|
||||
| `done` | Complete, no follow-up remains |
|
||||
| `cancelled` | Intentionally abandoned |
|
||||
|
||||
* **Production / Demo:** namespace `groombook`, FQDN `demo.groombook.dev`
|
||||
* **UAT:** namespace `groombook-uat`, FQDN `uat.groombook.dev`
|
||||
* **Dev:** namespace `groombook-dev`, FQDN `dev.groombook.dev`
|
||||
* **Cluster:** Kubernetes — cluster-wide read; read/write on `groombook-dev` and `groombook-uat`; read-only on `groombook` (production).
|
||||
* **Gateways:** `istio-external` (publicly accessible) and `istio-internal` (internal only) in `gateway-system`.
|
||||
* **Container registry:** `ghcr.io/groombook/<service>` only.
|
||||
**Never use `in_review` for handoffs.** It does not trigger inbox-lite and the receiving agent will not wake.
|
||||
|
||||
## Authentication
|
||||
## Status Transition Rules
|
||||
|
||||
* **Framework:** Better-Auth.
|
||||
* **Social login:** Google and Apple OAuth.
|
||||
* **SSO:** Authentik OIDC at `https://auth.farh.net` (credentials in `authentik-credentials` secret).
|
||||
* **Never build custom authentication.**
|
||||
| Handoff | Correct Status |
|
||||
|---------|----------------|
|
||||
| Engineer → UAT (Patty) | `todo` |
|
||||
| UAT (Patty) → QA (Regina) | `todo` |
|
||||
| QA (Regina) → CTO (Nancy) | `todo` |
|
||||
| CTO (Nancy) → CEO (Countess) | `todo` |
|
||||
| Any failure → Engineer | `todo` |
|
||||
| CEO rejection → CTO (Nancy) | `todo` |
|
||||
| CTO (Nancy) → Engineer (fix) | `todo` |
|
||||
|
||||
## Deployment — 2-stage Flux GitOps
|
||||
## CI/CD
|
||||
|
||||
**Stage 1 — CI (Gitea Actions, uses GitHub Actions-compatible YAML syntax, runs in each application repo):**
|
||||
- Triggered automatically on every merge to `main`
|
||||
- Builds and tags the Docker image
|
||||
- Pushes tagged images to `ghcr.io/groombook/<service>`
|
||||
- CI runs on self-hosted ARC runners: `runs-on: runners-privilegedescalation`
|
||||
- Only Hugh Hackman has write access to `.github/workflows/` files
|
||||
- All CI/CD workflow changes must be delegated to Hugh
|
||||
- Runners scale to zero when idle and start automatically when a workflow triggers
|
||||
|
||||
**Stage 2 — GitOps (Flux, managed externally):**
|
||||
- Flux watches `groombook/infra` as the **target** GitRepository — it is **not** a Flux bootstrap/cluster repo.
|
||||
- Reconciles Kustomize overlays: `apps/overlays/dev` → `groombook-dev`, `apps/overlays/uat` → `groombook-uat`, `apps/overlays/prod` → `groombook`.
|
||||
## Security Review
|
||||
|
||||
**Policy — Flux Image Tag Automation is DENIED.** Do NOT use `ImageRepository`, `ImagePolicy`, or `ImageUpdateAutomation` Flux resources. Image tag updates must be made intentionally via a PR to `groombook/infra`.
|
||||
Security review is handled as part of the CTO review stage. Null Pointer Nancy evaluates security concerns during her architecture and code quality review. There is no separate dedicated security review agent.
|
||||
|
||||
**To deploy a change:**
|
||||
1. Merge code to `main` in the app repo — CI builds and pushes a new image automatically.
|
||||
2. Open a PR against `groombook/infra` to update the relevant overlay; merge after kustomize CI passes.
|
||||
3. Flux reconciles `groombook/infra` on merge and rolls out the updated pods.
|
||||
## Work Distribution
|
||||
|
||||
**To force a rollout** (pick up new `:latest` on stuck pods):
|
||||
```bash
|
||||
kubectl rollout restart deployment/<name> -n <namespace>
|
||||
```
|
||||
|
||||
## Infrastructure as Code
|
||||
|
||||
Terraform / OpenTofu is deployed via the **Flux OpenTofu Controller** in a GitOps fashion. Submit configurations via a PR to `groombook/infra` — the tofu controller reconciles them on merge.
|
||||
|
||||
**Never run `tofu` directly.** Never `kubectl apply` against production. Production changes go through Flux only.
|
||||
|
||||
## Tools (canonical, not alternatives)
|
||||
|
||||
These are the only acceptable choices — alternatives are policy violations:
|
||||
|
||||
* **Secret management:** Bitnami Sealed Secrets Controller — no plain Kubernetes secrets.
|
||||
* **Database:** CloudNativePG Operator (Postgres) — no SQLite, MariaDB, or MySQL.
|
||||
* **Cache / pub-sub:** DragonflyDB Operator — no Redis.
|
||||
* **Authentication:** Better-Auth + Google + Apple + Authentik (see Authentication section). Never build custom auth.
|
||||
* **Dependency updates:** Mend Renovate. **Dependabot is not used and will not be used.**
|
||||
* **Container registry:** `ghcr.io/groombook/<service>` — no Docker Hub for first-party images.
|
||||
|
||||
If a task requires deviating from any of the above, treat it as a destructive action: stop, file an issue with rationale, request board approval.
|
||||
|
||||
## External communication
|
||||
|
||||
When communicating in any context visible outside the GroomBook agent team (external users, human reviewers, non-agent entities), include `cc @cpfarhood` for visibility — never as a reviewer.
|
||||
|
||||
## No self-merge
|
||||
|
||||
No agent merges their own PR. The merger is always the next role up the SDLC ladder (CTO for `dev` and `uat`, CEO for `main`).
|
||||
- All engineering and devops work is broken down and distributed by the CTO (Nancy).
|
||||
- Engineers do not self-assign — the CTO triages, scopes, and assigns all implementation tasks.
|
||||
- Hugh Hackman owns CI/CD, infrastructure, and pipeline work.
|
||||
- Gandalf the Greybeard owns plugin implementation.
|
||||
- Regression Regina owns QA review and test coverage.
|
||||
- Pixel Patty owns UAT/E2E browser testing.
|
||||
|
||||
Reference in New Issue
Block a user