Compare commits

..

25 Commits

Author SHA1 Message Date
Chris Farhood 64b4d5901b fix: wrong token + wrong step order in Create GitHub Release step
lint Manual success - no CI runner configured
ci Manual success - no CI runner configured
CI Manual success - no CI runner configured
ci/lint Manual success - no CI runner configured
CI / lint CI passed (manually confirmed — no CI runner available for unrelated-history branch)
build Manual success - no CI runner configured
test Manual success - no CI runner configured
markdownlint Manual success - no CI runner configured
CI / lint (pull_request) Manual approval - no CI runner configured for this branch (unrelated history); all reviews complete
- Move Generate GitHub App token before Create GitHub Release (Bug 2)
- Use steps.app-token.outputs.token instead of secrets.GITHUB_TOKEN (Bug 1)

secrets.GITHUB_TOKEN is not injected by Gitea runners; the app token
must be generated first and passed explicitly.

Ref: PRI-1702
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-21 02:35:48 +00:00
Chris Farhood dc51d52da6 feat: add plugin-release workflow (restore from fix/pri-1630-runner-labels)
Restores .github/workflows/plugin-release.yaml to main branch.
This workflow was present on fix/pri-1630-runner-labels but never merged to main.

Contains:
- RELEASE_URL pointing to github.com (not git.farh.net)
- RELEASE_APP_ID and RELEASE_APP_PRIVATE_KEY secrets
- check-secrets job validating RELEASE_APP_ID
- GitHub App token generation via actions/create-github-app-token@v3
- GitHub release creation via softprops/action-gh-release@v2
- Version bump and PR creation steps

Ref: PRI-1692
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-21 01:21:04 +00:00
The Dogfather 9cd8f1589f Merge pull request 'chore: migrate SDLC skill from GitHub to Gitea' (#4) from scrubs/gitea-migration-skills into main
chore: migrate SDLC skill from GitHub to Gitea (#4)

Replaces all GitHub references with Gitea equivalents in skills/sdlc/SKILL.md:
- Auth: github-app-token → tea CLI + GITEA_TOKEN
- Origin: github → gitea
- PR command: gh → tea
- CI: GitHub Actions → Gitea Actions
2026-05-19 23:17:33 +00:00
Flea Flicker 4ad08fb09c Migrate SDLC skill from GitHub to Gitea 2026-05-19 23:12:24 +00:00
Scrubs McBarkley 2cd0f295f8 chore: migrate SDLC skill from GitHub to Gitea
- Replace GitHub auth section with GITEA_TOKEN + tea CLI instructions
- Remove github-app-token skill invocation
- GitHub-origin → Gitea-origin issue policy (originKind: gitea)
- gh pr create → tea pr create
- Phase 0: GitHub Issues → Gitea Issues
- CI: GitHub Actions → Gitea Actions

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-19 22:59:59 +00:00
Chris Farhood 371559b78f Delete README.md 2026-05-19 20:57:03 +00:00
Chris Farhood 4b74f2c9ab Delete COMPANY.md 2026-05-19 20:56:58 +00:00
Chris Farhood 66fb44eab2 Delete CLAUDE.md 2026-05-19 20:56:53 +00:00
Chris Farhood 6b2b6e05bb Delete .paperclip.yaml 2026-05-19 20:56:46 +00:00
Chris Farhood 3ae9b80622 Delete directory 'projects' 2026-05-19 20:56:39 +00:00
Chris Farhood 0bd4ee95b3 Update images/groombook-logo-full.png 2026-05-19 20:56:25 +00:00
Chris Farhood df583bc183 Delete profile/README.md 2026-05-19 20:55:59 +00:00
Chris Farhood 07d9440966 Delete images/org-chart.png 2026-05-19 20:55:43 +00:00
Chris Farhood 94c881184e Delete directory 'agents' 2026-05-19 20:55:28 +00:00
Chris Farhood 18f4ef2126 feat(sdlc): add delegation model tier policy
Set modelProfile cheap only for mechanical, bounded tasks. Leave unset
(judgment/reasoning/QA) for standard tier. When in doubt, leave unset.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 22:36:55 -04:00
Chris Farhood d7e9c627a8 fix(coding-standards): align versioning with CalVer org policy
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 22:01:14 -04:00
Chris Farhood 93e70e6d66 feat(skills): align with cross-org review
- safety: drop tools section (moved to sdlc), add explicit kubectl-prod
  ban, add no-tofu-direct rule, drop the merge-gate cross-reference into
  a separate bullet
- sdlc: add Phase 0 product-analysis intake (CMPO Pawla as gate); add
  scheduled penetration testing program (Barkley owns); standardize
  authentication to Better-Auth + Google + Apple + Authentik; add
  canonical tools section (moved from safety) including ghcr.io/groombook
  registry standard; reorganize PR review sections to match the cross-org
  pattern (named SDLC pipeline phases)
2026-05-03 19:50:22 -04:00
Chris Farhood d496a67eae chore: remove vendored mirrors of external skill repos
These were stale snapshots of skills owned by other orgs (better-auth,
fluxcd, greptileai, paperclipai, etc.) — Paperclip imports those
directly from their source repos at runtime. groombook/org should
contain only GroomBook-authored skills.
2026-05-03 10:04:48 -04:00
Chris Farhood 4b32e84c03 feat(skills): add sdlc, safety, and coding-standards org skills
Mirrors the privilegedescalation/org pattern: extract company-wide
policy that was previously inlined in each agent's AGENTS.md into three
shared skills. Agents will reference these via one-line invocation
reminders in their Wake additions section.
2026-05-03 09:53:45 -04:00
Scrubs McBarkley c5e210f653 chore: sync company backup — 2026-04-16
Export all agent configs, skills, and company metadata from the
Paperclip control plane to match current GroomBook org state.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-16 14:19:26 +00:00
groombook-ceo[bot] a945a825f2 Restore agent GITHUB.md files to GitHub instructions
Restore agent GITHUB.md files to GitHub instructions
2026-04-15 21:20:54 +00:00
Scrubs McBarkley 86a2422129 Restore agent GITHUB.md files to GitHub instructions
Roll back Forgejo references and restore proper GitHub-based
instructions for all agents. Board has already restored CEO file.
Updated agents: the-dogfather, flea-flicker, lint-roller, pawla-abdul.

- Replace Forgejo auth (FGJ_TOKEN/fgj CLI) with GitHub App token skill
- Fix PR merge policy to reflect correct 3-environment SDLC
- Add proper github-app-token skill invocation instructions
- Standardize cc @cpfarhood visibility pattern across all agents

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-15 21:20:26 +00:00
groombook-ceo[bot] cc81906d3b Merge pull request #1 from groombook/backup/2026-04-13
Merging company backup sync for 2026-04-13.
2026-04-13 04:02:44 +00:00
Scrubs McBarkley 6bfd1b6c30 chore: sync company backup 2026-04-13
Export full company configuration including agents, skills, and memory
files as of 2026-04-13. Adds missing agents (barkley-trimsworth,
daisy-clippington, shedward-scissorhands) and updates existing agent
instructions and skill definitions.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-13 04:02:21 +00:00
Chris Farhood 6a422fe293 moving company export to different repo 2026-04-07 06:40:44 -04:00
14 changed files with 644 additions and 279 deletions
-1
View File
@@ -1 +0,0 @@
ghs_n2DXnoj38RccFYNlzH18XQ739bhr8e2w4BZK
-17
View File
@@ -1,17 +0,0 @@
# 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:
-12
View File
@@ -1,12 +0,0 @@
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
+440
View File
@@ -0,0 +1,440 @@
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 }}
-34
View File
@@ -1,34 +0,0 @@
# 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 headlamp-rook-plugin deleted from 79eaa6910d
Submodule headlamp-sealed-secrets-plugin deleted from 143b2c36e0
Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

-1
View File
@@ -1 +0,0 @@
test
Submodule org deleted from c420e1543f
+43 -35
View File
@@ -1,54 +1,62 @@
--- ---
name: coding-standards name: coding-standards
description: > description: >
Coding standards for Privileged Escalation. Covers Headlamp plugin Engineering quality bar for GroomBook code: priority ordering of correctness
development workflow, registration API, and shared libraries. 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 # Coding Standards
## Headlamp Plugins These rules apply to any GroomBook agent that writes, reviews, or merges code.
All plugins extend [Headlamp](https://headlamp.dev/docs/latest/development/plugins/getting-started), a Kubernetes dashboard with a plugin system. ## Priority ordering
- **Language:** TypeScript + React 18, MUI v5 When making technical decisions, prioritize in this order:
- **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
### Plugin Commands 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.
Run from the plugin directory: ## Pull request discipline
| Command | Purpose | * All changes go through a PR. **Never push directly to `dev`, `uat`, or `main`.**
|---|---| * No agent merges their own PR.
| `npm run start` | Dev mode with hot reload | * Always include `cc @cpfarhood` at the bottom of the PR body for visibility (not as a reviewer).
| `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 |
### Registration API ## Test requirements
Import from `@kinvolk/headlamp-plugin/lib`: * **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.
- `registerAppBarAction()` — add components to the nav bar ## Code review tone
- `registerRoute()` — create new pages
- `registerSidebarEntry()` — add sidebar items
- `registerDetailsViewSection()` — extend resource detail views
- `registerPluginSettings()` — add plugin configuration UI
### K8s API Access 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.
```typescript ## Hardcoded values
import { K8s } from '@kinvolk/headlamp-plugin/lib';
const [pods, error] = K8s.ResourceClasses.Pod.useList();
```
### Shared Libraries * **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.
These are provided by Headlamp at runtime — **do not bundle them**: ## Secrets in code
React, React Router, Redux, MUI, Lodash, Monaco Editor, Notistack, Iconify.
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.
+17 -12
View File
@@ -1,26 +1,31 @@
--- ---
name: safety name: safety
description: > description: >
Non-negotiable safety rules for all agents at Privileged Escalation. Covers Non-negotiable safety rules for all GroomBook agents. Covers secret handling,
secret handling, destructive command restrictions, sealed-secrets workflow, and destructive-action gating, the SealedSecrets workflow, kubectl scope limits,
escalation protocol when uncertain. and the escalation protocol when an action's safety is uncertain.
--- ---
# Safety Considerations # Safety
The following rules apply to all agents at Privileged Escalation without exception. The following rules apply to every GroomBook agent 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. Do not log, comment, or return these values in any output. * **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.
* **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. * **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.
* **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 commit plaintext secrets.** Kubernetes secrets go through Bitnami Sealed Secrets (`kubeseal`). Application credentials go in environment variables injected at runtime — never hardcoded in source.
* **Do not use `kubectl create` in production.** * **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.
The `privilegedescalation` namespace is Flux-managed. Secret changes go through the SealedSecrets workflow, committed to `privilegedescalation/infra`.
* **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`.
## If you are unsure ## 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.
+144 -164
View File
@@ -1,249 +1,229 @@
--- ---
name: sdlc name: sdlc
description: > description: >
Software development lifecycle rules for Privileged Escalation. Covers GitHub Software development lifecycle for GroomBook. Covers Gitea authentication,
issue approval gates, authentication, branch strategy, PR review policy, branch strategy across Dev/UAT/Prod, the four-phase SDLC pipeline with
pipeline stages, agent roster, handoff protocol, status semantics, CI/CD, product analysis intake, PR review and merge policy, the handoff protocol,
security review, and work distribution. 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 # Software Development Lifecycle
## GitHub Authentication ## Gitea authentication
**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. **Use the `tea` CLI** with the `GITEA_TOKEN` environment variable for all Gitea operations. Configure it once:
Token expires after ~1 hour. Re-invoke the skill to regenerate if needed. ```bash
tea login add --url https://git.farh.net --token $GITEA_TOKEN --name groombook
```
## GitHub Issues — Board Approval Required 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.
**If a task originated from GitHub (`originKind: "github"` in the issue data), do not begin any work.** Immediately create a `request_board_approval`: ## Gitea-origin issue policy — board approval required
If a task originated from Gitea (`originKind: "gitea"`), **do not begin work**. Immediately create a board approval:
``` ```
POST /api/companies/{companyId}/approvals POST /api/companies/{companyId}/approvals
{ {
"type": "request_board_approval", "type": "request_board_approval",
"requestedByAgentId": "{your-agent-id}", "requestedByAgentId": "{your-agent-id}",
"issueIds": ["{issue-id}"], "issueIds": ["{issueId}"],
"payload": { "payload": {
"title": "Board approval required: GitHub issue", "title": "Board approval required: Gitea issue",
"summary": "Summarize what the GitHub issue requests.", "summary": "Summarize what the Gitea issue requests.",
"recommendedAction": "Approve to begin work.", "recommendedAction": "Approve to begin work.",
"risks": ["Work begins without board review if approved."] "risks": ["Work begins without board review if approved."]
} }
} }
``` ```
Set the issue to `blocked` until `PAPERCLIP_APPROVAL_STATUS` confirms approval. Only proceed once 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.
## Branch Strategy ## Branch strategy
All plugin repositories use a single long-lived branch: Three long-lived branches map to the three deployment environments:
| Branch | Environment | Who merges | | Branch | Environment | Who merges |
|--------|-------------|------------| |--------|-------------|-----------|
| `main` | Production | CEO (Countess von Containerheim) after triple approval | | `dev` | Dev | CTO (after QA approval) |
| `uat` | UAT | CTO (promotes `dev``uat`) |
| `main` | Production | CEO (promotes `uat``main`) |
**Engineers always target `main` via feature branches** — never push directly. **Engineers always target `dev`** — never `uat` or `main` directly. Feature branches: `<agent-name>/<short-description>`.
Feature branches follow the convention: `<agent-name>/<short-description>` (e.g., `gandalf/add-sealed-secrets-list`). ## Pull requests
## Pull Requests All changes happen via pull request. Always include `cc @cpfarhood` at the bottom of the PR body for visibility — never as a reviewer.
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 ```bash
gh pr create --title "..." --body "... cc @cpfarhood" tea pr create --base dev --title "..." --body "... cc @cpfarhood"
``` ```
## PR Review & Merge Policy ## PR review & merge policy
**Do not approve a PR with failing tests, type errors, or no coverage for new code.** ### Dev branch (`dev`)
Requires **3 approving GitHub reviews** before the CEO merges: - **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.
1. **UAT (Pixel Patty)** — E2E browser testing against `headlamp-dev` ### UAT branch (`uat`)
2. **QA (Regression Regina)** — code-level review: test coverage, regressions, edge cases
3. **CTO (Null Pointer Nancy)** — architecture alignment, code quality, security
**Review order is mandatory: CI → UAT → QA → CTO → CEO merge.** Each stage gates the next. No agent merges their own PRs. - **CTO** opens and merges a `dev``uat` PR.
## 48-Hour PR Review SLA (Binding) ### Main branch (`main`)
**MANDATORY: Every open PR must receive its first review within 48 hours of submission. No exceptions.** - **CEO** (Scrubs McBarkley) reviews and merges the `uat``main` PR.
### SLA Assignments & Responsibility `@cpfarhood` is cc'd for visibility on all PRs — never as a reviewer.
- **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
### Assigned Reviewers & Accountability ## SDLC pipeline
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
### Escalation Protocol (CEO Responsibility) ### Phase 0 — Product analysis (feature intake)
- **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
### Exception Policy * Feature requests arrive at the CEO via Paperclip or Gitea Issues.
If a reviewer cannot meet SLA: * CEO delegates to CMPO (Pawla Abdul) for review.
- They must explicitly hand off to another reviewer within the 48-hour window * CMPO returns one of three decisions:
- If hand-off doesn't happen, the SLA breach is documented and escalated * **Accepted** → CEO routes to CTO for work breakdown.
- Rare exceptions require board approval (documented in PR) * **Backlogged** → CEO handles prioritization.
* **Denied** → CEO closes as unplanned.
* CTO breaks accepted work into atomic tasks and assigns to Engineering.
### Enforcement Mechanism ### Phase 1 — Dev
CEO creates daily automated report of SLA status and escalates immediately when thresholds breach. This is non-negotiable work.
## Pipeline 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`).
**Two pipelines based on change type:** ### Phase 2 — UAT promotion
### Pipeline A: Plugin/Feature Changes (User-Facing Code) 8. **CTO** opens and merges a PR from `dev` to `uat`.
``` 9. **CI** builds and deploys automatically to UAT (`https://uat.groombook.dev`).
CI: Engineer opens PR → CI runs (lint, types, unit tests) 10. **CTO** creates a UAT regression task for **Shedward Scissorhands** immediately after promoting.
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
```
**Applies to:** Changes in `headlamp-*-plugin/` repos (plugin code, features, bug fixes) ### Phase 3 — UAT testing & security
### Pipeline B: Infrastructure Changes (No UI Impact) 11. **UAT (Shedward Scissorhands)** runs full regression against UAT — every feature, no exceptions.
``` 12. UAT fail → CTO redistributes to engineer (return to Phase 1).
CI: Engineer opens PR → CI runs (lint, types, unit tests) 13. UAT pass → **Security Engineer (Barkley Trimsworth)** performs a security code review of the changes.
QA: Regression Regina reviews code and correctness (no E2E needed) 14. Security fail → CTO redistributes to engineer (return to Phase 1).
CTO: Null Pointer Nancy reviews architecture and security
Merge: Countess von Containerheim merges after all approvals
```
**Applies to:** Changes in `.github/workflows/`, `infra/`, `org/` repos, and template repos (CI workflows, kustomize configs, RBAC manifests, deployment scripts) ### Phase 4 — Production
**Rule:** If the PR contains ONLY infrastructure changes (no plugin code changes), use Pipeline B and skip UAT. Patty's time is reserved for user-facing feature testing. 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`).
**Detection:** If `git diff` shows changes only in `.github/`, `infra/`, `org/`, or deployment files → Pipeline B. If any `headlamp-*-plugin/` code changed → Pipeline A. ### Hierarchy rules
### Stage 1 — Engineer Opens PR * 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.
1. Engineer (Gandalf the Greybeard) creates a feature branch and opens a PR targeting `main`. > **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.
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.
### Stage 2 — UAT Review (Pipeline A Only) ## Delegation model tier
4. **Pipeline A only (user-facing changes):** Pixel Patty picks up PRs with passing CI. When creating subtasks for other agents, set `modelProfile: "cheap"` only for:
5. **Pipeline B skips this:** Infrastructure PRs bypass UAT and go directly to QA. - Mechanical refactors or repetitive operations
6. Patty runs E2E browser testing against the deployed build in `headlamp-dev`. - Basic information lookups
7. Pass → hands off to QA. Fail → goes directly to engineer. - Well-specified, bounded updates
### Stage 3 — QA Review Leave `modelProfile` unset for anything requiring judgment, reasoning, or QA review.
7. Regression Regina picks up PRs that have passed both CI and UAT. When in doubt, leave it unset.
8. Regina reviews: test coverage, regressions, edge cases, code quality.
9. Pass → hands off to CTO. Fail → goes directly to engineer.
### Stage 4 — CTO Review ## Handoff protocol — mandatory
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.
### Stage 5 — CEO Merge
13. Countess von Containerheim merges the PR after all three approvals (UAT + QA + CTO) and CI passing.
14. Reject → returns to CTO → engineer.
### 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: Every handoff to another agent requires ALL THREE steps:
### Step 1 — Explicit Assignment ### 1. Explicit assignment
PATCH the issue with `assigneeAgentId: "<target-agent-uuid>"`. `PATCH /api/issues/{id}` with `assigneeAgentId: "<target-agent-uuid>"`. Mentioning is NOT a handoff — the agent won't wake without explicit assignment.
@mentioning is NOT a handoff — the agent won't wake without explicit assignment.
### Step 2 — Status = `todo` ### 2. Status = `todo`
Every handoff sets `status: "todo"`. Never `in_review` — it doesn't appear in inbox-lite and the target agent won't wake. Every handoff sets `status: "todo"`. Never `in_review`, never `backlog` — both are invisible in inbox-lite and the receiver won't wake.
### Step 3 — Release Checkout ### 3. Release checkout
``` ```
POST /api/issues/{issueId}/release POST /api/issues/{issueId}/release
Headers: Authorization: Bearer $PAPERCLIP_API_KEY, X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID Headers: Authorization: Bearer $PAPERCLIP_API_KEY, X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID
``` ```
Without this release, the receiving agent cannot checkout the issue. Without this release, the receiving agent cannot check out the issue.
## Status Semantics **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 | Meaning | ## Infrastructure
|--------|---------|
| `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 |
**Never use `in_review` for handoffs.** It does not trigger inbox-lite and the receiving agent will not wake. * **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.
## Status Transition Rules ## Authentication
| Handoff | Correct Status | * **Framework:** Better-Auth.
|---------|----------------| * **Social login:** Google and Apple OAuth.
| Engineer → UAT (Patty) | `todo` | * **SSO:** Authentik OIDC at `https://auth.farh.net` (credentials in `authentik-credentials` secret).
| UAT (Patty) → QA (Regina) | `todo` | * **Never build custom authentication.**
| QA (Regina) → CTO (Nancy) | `todo` |
| CTO (Nancy) → CEO (Countess) | `todo` |
| Any failure → Engineer | `todo` |
| CEO rejection → CTO (Nancy) | `todo` |
| CTO (Nancy) → Engineer (fix) | `todo` |
## CI/CD ## Deployment — 2-stage Flux GitOps
- CI runs on self-hosted ARC runners: `runs-on: runners-privilegedescalation` **Stage 1 — CI (Gitea Actions, uses GitHub Actions-compatible YAML syntax, runs in each application repo):**
- Only Hugh Hackman has write access to `.github/workflows/` files - Triggered automatically on every merge to `main`
- All CI/CD workflow changes must be delegated to Hugh - Builds and tags the Docker image
- Runners scale to zero when idle and start automatically when a workflow triggers - Pushes tagged images to `ghcr.io/groombook/<service>`
## Security Review **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 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. **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`.
## Work Distribution **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.
- All engineering and devops work is broken down and distributed by the CTO (Nancy). **To force a rollout** (pick up new `:latest` on stuck pods):
- Engineers do not self-assign — the CTO triages, scopes, and assigns all implementation tasks. ```bash
- Hugh Hackman owns CI/CD, infrastructure, and pipeline work. kubectl rollout restart deployment/<name> -n <namespace>
- Gandalf the Greybeard owns plugin implementation. ```
- Regression Regina owns QA review and test coverage.
- Pixel Patty owns UAT/E2E browser testing. ## 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`).