Compare commits

..

2 Commits

Author SHA1 Message Date
Chris Farhood 613f570bdc Implement two-pipeline PR review system
Add Pipeline A (user-facing features) and Pipeline B (infrastructure-only) to eliminate unnecessary UAT delays for non-UI changes.

Pipeline A: CI → UAT → QA → CTO → merge (for plugin code)
Pipeline B: CI → QA → CTO → merge (for .github, infra, org, templates)

Detection rule: If PR only changes .github/, infra/, org/ → Pipeline B, skip Patty's UAT review.
This frees Patty to focus on plugin E2E testing and unblocks the infra queue immediately.

Unblocks stalled issues like PRI-486 (kustomize fix, 2h+ stalled waiting for unnecessary UAT).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-06 13:50:28 +00:00
Chris Farhood 12ccf82454 Revise PR review SLA: remove threat language, focus on visibility and process
Replace dismissal-threat framing with operational consequences:
- 24h: public visibility + status flag
- 48h: merge queue block + escalation
- 72h+: blocks release if critical-path
- Exceptions: documented hand-off, not absolute prohibition

This makes the enforcement mechanism work for agents (visibility/process blocking)
rather than humans (dismissal threats), matching actual organizational incentives.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-05-05 10:53:13 +00:00
28 changed files with 397 additions and 823 deletions
+1
View File
@@ -0,0 +1 @@
ghs_n2DXnoj38RccFYNlzH18XQ739bhr8e2w4BZK
+17
View File
@@ -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:
+12
View File
@@ -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
View File
@@ -1 +0,0 @@
github: [privilegedescalation]
-2
View File
@@ -1,2 +0,0 @@
self-hosted-runner:
labels: []
-132
View File
@@ -1,132 +0,0 @@
#!/usr/bin/env bash
# ci-health-check.sh — Scan all privilegedescalation repos for CI/CD health
# Run from: /paperclip/privilegedescalation/engineering/hugh
# Requires: GH_TOKEN set (use: export GH_TOKEN=$(bash ./get-github-token.sh))
#
# Plugin repo discovery
# ---------------------
# PLUGIN_REPOS is populated dynamically from the GitHub org so newly created
# plugin repos are picked up automatically. The filter is:
# - non-archived, public repos in the privilegedescalation org
# - name starts with "headlamp-"
# - excludes "headlamp-agent-skills" (skills bundle, not a Headlamp plugin)
# If discovery fails (network error, GH_TOKEN missing, API outage), we fall
# back to a hardcoded list so the health check still produces a useful report.
#
# Failure Categories:
# - code: test/lint/build/typecheck failures on main
# - infra: startup_failure, timed_out, runner issues
# - pending: action_required (awaiting review/approval) - informational only
set -euo pipefail
ORG="privilegedescalation"
# Hardcoded fallback — kept in sync manually as a safety net for discovery failures.
PLUGIN_REPOS_FALLBACK=(
headlamp-polaris-plugin
headlamp-rook-plugin
headlamp-sealed-secrets-plugin
headlamp-intel-gpu-plugin
headlamp-tns-csi-plugin
headlamp-kube-vip-plugin
headlamp-plugin-template
headlamp-argocd-plugin
)
mapfile -t PLUGIN_REPOS < <(
gh api --paginate "orgs/${ORG}/repos" \
--jq '.[] | select(.archived == false and .visibility == "public" and (.name | startswith("headlamp-")) and .name != "headlamp-agent-skills") | .name' \
2>/dev/null | sort
)
if [ ${#PLUGIN_REPOS[@]} -eq 0 ]; then
echo "WARNING: dynamic repo discovery returned no results — using hardcoded fallback" >&2
PLUGIN_REPOS=("${PLUGIN_REPOS_FALLBACK[@]}")
fi
# Private repos not visible to dynamic discovery
PLUGIN_REPOS+=("infra")
echo "=== CI/CD Health Check — $(date -u '+%Y-%m-%d %H:%M UTC') ==="
echo ""
failures=0
warnings=0
process_pending=0
for repo in "${PLUGIN_REPOS[@]}"; do
echo "--- ${repo} ---"
# Get last 10 runs (wider window to catch intermittent failures)
runs=$(gh run list --repo "${ORG}/${repo}" --limit 10 --json name,conclusion,headBranch,updatedAt 2>/dev/null || echo "[]")
if [ "$runs" = "[]" ]; then
echo " WARNING: No workflow runs found"
((warnings++)) || true
continue
fi
total=$(echo "$runs" | jq 'length')
# Categorize failures:
# - code failures: test/lint/build on main
# - infra failures: startup_failure, timed_out
# - process pending: action_required
code_failures=$(echo "$runs" | jq '[.[] | select(.headBranch=="main" and .conclusion=="failure" and .name!="Release" and .name!="E2E Tests")] | length')
infra_failures=$(echo "$runs" | jq '[.[] | select(.conclusion=="startup_failure" or .conclusion=="timed_out")] | length')
action_required=$(echo "$runs" | jq '[.[] | select(.conclusion=="action_required")] | length')
if [ "$code_failures" -gt 0 ]; then
echo " FAIL (code): ${code_failures} CI failure(s) in last ${total} runs on main:"
echo "$runs" | jq -r '.[] | select(.headBranch=="main" and .conclusion=="failure" and .name!="Release" and .name!="E2E Tests") | " - \(.name) (\(.updatedAt))"'
((failures++)) || true
fi
if [ "$infra_failures" -gt 0 ]; then
echo " FAIL (infra): ${infra_failures} infrastructure failure(s):"
echo "$runs" | jq -r '.[] | select(.conclusion=="startup_failure" or .conclusion=="timed_out") | " - \(.name): \(.conclusion) (\(.updatedAt))"'
((failures++)) || true
fi
if [ "$code_failures" -eq 0 ] && [ "$infra_failures" -eq 0 ]; then
echo " OK: CI passing on main"
fi
# Process pending — informational only (awaiting review/approval)
if [ "$action_required" -gt 0 ]; then
echo " INFO: ${action_required} workflow run(s) awaiting action (dual approval, review, etc.):"
echo "$runs" | jq -r '.[] | select(.conclusion=="action_required") | " - \(.name) on \(.headBranch) (\(.updatedAt))"'
((process_pending++)) || true
fi
# Surface E2E test failures as warnings (infra blocker: RBAC not yet applied — PRI-494)
e2e_failures=$(echo "$runs" | jq '[.[] | select(.headBranch=="main" and .name=="E2E Tests" and .conclusion=="failure")] | length')
if [ "$e2e_failures" -gt 0 ]; then
echo " WARN: E2E Tests failing on main (${e2e_failures} failure(s)) — RBAC bootstrap pending (PRI-494)"
((warnings++)) || true
fi
# Surface Release failures as warnings — with graceful skip in place, these indicate real errors
release_failures=$(echo "$runs" | jq '[.[] | select(.name=="Release" and .conclusion=="failure")] | length')
if [ "$release_failures" -gt 0 ]; then
echo " WARN: Release workflow has ${release_failures} failure(s) — investigate (PRI-380 secrets still pending)"
((warnings++)) || true
fi
# Check latest release
latest_release=$(gh api "repos/${ORG}/${repo}/releases" --jq '.[0].tag_name // "none"' 2>/dev/null || echo "error")
echo " Latest release: ${latest_release}"
echo ""
done
echo "=== Summary ==="
echo "Repos scanned: ${#PLUGIN_REPOS[@]}"
echo "With failures: ${failures}"
echo "With warnings: ${warnings}"
echo "With pending approval: ${process_pending}"
if [ "$failures" -gt 0 ]; then
exit 1
fi
-84
View File
@@ -1,84 +0,0 @@
# GitHub Actions Workflows
This directory contains reusable and repo-specific GitHub Actions workflows for the privilegedescalation organization.
## Available Tools on Runners
### Always Available
- `curl` - HTTP client (use this instead of `gh` CLI for API calls)
- `jq` - JSON processor
- `bash` - Shell
- `git` - Version control
- `docker` / `podman` - Container runtime (depending on runner)
### NOT Available (must install if needed)
- `gh` CLI - GitHub CLI is **not** pre-installed on runners. Use `curl` with the GitHub API instead.
## Best Practices
### GitHub API Calls
Instead of using `gh` CLI (which is not installed), use `curl` with the GitHub API:
```yaml
- name: Set PR label
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
curl -sf \
-X POST \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/labels" \
-d '{"labels":["label-name"]}'
```
### Workflow Validation
Run actionlint locally before pushing:
```bash
actionlint -color .github/workflows/*.yaml
```
### Reusable Workflows
- `plugin-ci.yaml` - Standard CI for Headlamp plugins
- `plugin-e2e.yaml` - E2E testing for Headlamp plugins
- `dual-approval-check.yaml` - Checks for CTO and QA approval
- `detect-pr-pipeline.yaml` - Detects Pipeline A vs Pipeline B based on changed files
## Workflow Naming Convention
- Use kebab-case: `my-workflow.yaml`
- Be descriptive: `plugin-ci.yaml` not `ci.yaml`
- For reusable workflows, keep the name clear about its purpose
## Required Gates
All PRs must pass:
1. `actionlint` validation (workflow YAML syntax)
2. Shell script validation (if scripts are used)
3. Any repo-specific CI checks
## Common Patterns
### Getting Changed Files
Use `tj-actions/changed-files`:
```yaml
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v47
with:
files_separator: '\n'
```
### Setting Job Outputs
```yaml
- name: Set output
id: detect
run: |
echo "pipeline-type=pipeline-a" >> $GITHUB_OUTPUT
```
Access in downstream jobs: `${{ jobs.job-name.outputs.pipeline-type }}`
-22
View File
@@ -1,22 +0,0 @@
name: CI/CD Health Check
on:
schedule:
- cron: '0 8 * * 1-5' # Every weekday at 8 AM UTC
workflow_dispatch:
jobs:
health-check:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Run CI/CD health check
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
if [ -z "$GITEA_TOKEN" ]; then
echo "::warning::GITEA_TOKEN not configured — health check may have limited data."
fi
./.github/scripts/ci-health-check.sh
-82
View File
@@ -1,82 +0,0 @@
name: Detect PR Pipeline Type
on:
pull_request:
branches: [main, dev, uat]
workflow_call:
permissions:
contents: read
pull-requests: write
jobs:
test-detection-logic:
runs-on: ubuntu-latest
timeout-minutes: 2
env:
HEAD_REF: ${{ github.head_ref }}
BASE_REF: ${{ github.base_ref }}
steps:
- name: Checkout
run: |
git clone --depth=1 "https://x-access-token:${{ secrets.GITEA_TOKEN }}@git.farh.net/${{ github.repository }}.git" .
git fetch origin "$BASE_REF" --depth=1
git fetch origin +refs/pull/*/head:refs/pull/*/head --depth=1
git checkout "${{ github.sha }}"
- name: Run detection tests
run: bash scripts/test-detect-pipeline.sh
detect-pipeline:
runs-on: ubuntu-latest
timeout-minutes: 5
env:
HEAD_REF: ${{ github.head_ref }}
BASE_REF: ${{ github.base_ref }}
outputs:
pipeline-type: ${{ steps.detect.outputs.pipeline-type }}
steps:
- name: Checkout
run: |
git clone --depth=1 "https://x-access-token:${{ secrets.GITEA_TOKEN }}@git.farh.net/${{ github.repository }}.git" .
git fetch origin "$BASE_REF" --depth=1
git fetch origin +refs/pull/*/head:refs/pull/*/head --depth=1
git checkout "${{ github.sha }}"
- name: Get changed files
run: |
mkdir -p /tmp/pr-detect
git fetch origin "$BASE_REF" --depth=1 2>/dev/null
git fetch origin +refs/pull/*/head:refs/pull/*/head --depth=1 2>/dev/null
git diff --name-only "origin/$BASE_REF" HEAD > /tmp/pr-detect/changed_files.txt
echo "Files found: $(wc -l < /tmp/pr-detect/changed_files.txt)"
cat /tmp/pr-detect/changed_files.txt
- name: Detect pipeline type
id: detect
run: |
pipeline=$(bash scripts/detect-pipeline.sh < /tmp/pr-detect/changed_files.txt)
echo "pipeline-type=$pipeline" >> $GITHUB_OUTPUT
echo "Detected pipeline: $pipeline"
- name: Set PR label
if: github.event_name == 'pull_request'
continue-on-error: true
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PIPELINE_TYPE: ${{ steps.detect.outputs.pipeline-type }}
run: |
curl -sf \
-X POST \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github.v3+json" \
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/labels" \
-d "{\"labels\":[\"${PIPELINE_TYPE}\"]}"
-52
View File
@@ -1,52 +0,0 @@
name: PR Validation
on:
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
env:
HEAD_REF: ${{ github.head_ref }}
BASE_REF: ${{ github.base_ref }}
run: |
git clone --depth=1 "https://x-access-token:${{ secrets.GITEA_TOKEN }}@git.farh.net/${{ github.repository }}.git" .
git fetch origin "$BASE_REF" --depth=1
git fetch origin +refs/pull/*/head:refs/pull/*/head --depth=1
git checkout "${{ github.sha }}"
- name: Install actionlint
run: |
ACTIONLINT_VERSION="1.7.7"
mkdir -p "$HOME/.local/bin"
apt-get install -y wget -qq >/dev/null 2>&1 || true
wget -qO- "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
| tar -xz -C "$HOME/.local/bin" actionlint
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Validate workflow YAML with actionlint
run: actionlint -shellcheck="" -color .github/workflows/*.yaml
- name: Install shellcheck
run: |
SC_VERSION="v0.10.0"
mkdir -p "$HOME/.local/bin"
wget -qO- "https://github.com/koalaman/shellcheck/releases/download/${SC_VERSION}/shellcheck-${SC_VERSION}.linux.x86_64.tar.xz" \
| tar -xJ --strip-components=1 -C "$HOME/.local/bin" "shellcheck-${SC_VERSION}/shellcheck"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Shellcheck scripts
run: |
if ls .github/scripts/*.sh 1>/dev/null 2>&1; then
for script in .github/scripts/*.sh; do
echo "Checking ${script}..."
shellcheck --severity=warning "$script"
done
else
echo "No shell scripts to check"
fi
-27
View File
@@ -1,27 +0,0 @@
name: Renovate
on:
schedule:
- cron: '0 2 * * 6' # Saturday 2:00 UTC — aligns with "every weekend" in renovate-config.json
workflow_dispatch:
jobs:
renovate:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Run Renovate
env:
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: https://git.farh.net
RENOVATE_AUTODISCOVER: "true"
LOG_LEVEL: debug
run: |
npx renovate \
--token="${RENOVATE_TOKEN}" \
--platform=gitea \
--endpoint=https://git.farh.net \
--configurationFile=renovate-config.json
@@ -1,66 +0,0 @@
name: Stale Release Branch Cleanup
on:
schedule:
- cron: '0 9 * * 1' # Weekly every Monday at 09:00 UTC
workflow_dispatch:
inputs:
dry_run:
description: 'Dry run (no changes made)'
required: false
default: false
type: boolean
jobs:
cleanup-stale-branches:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
sparse-checkout: |
.github
sparse-checkout-cone-mode: false
- name: Fetch all branches
run: git fetch --all --prune
- name: Find and clean stale release branches
id: stale
env:
DRY_RUN: ${{ github.event.inputs.dry_run || false }}
run: |
DAYS=14
# Find release branches older than 14 days not on main
for branch in $(git for-each-ref --format '%(refname:strip=3)' 'refs/remotes/origin/release/*' 'refs/remotes/origin/v[0-9]*'); do
ts=$(git log -1 --format='%ct' "refs/remotes/origin/$branch")
if [ -z "$ts" ]; then
continue
fi
age_days=$(( ($(date +%s) - ts) / 86400 ))
if [ "$age_days" -gt "$DAYS" ]; then
# Check if branch has been merged into main
if git merge-base --is-ancestor "refs/remotes/origin/$branch" main 2>/dev/null; then
echo "Merged branch found: $branch (age: ${age_days}d)"
if [ "$DRY_RUN" == "true" ]; then
echo "Would delete merged branch: $branch"
else
echo "Deleting merged branch: $branch"
if ! git push origin --delete "$branch" 2>&1; then
echo "::warning::Failed to delete branch: $branch"
fi
fi
fi
fi
done
- name: Report dry run results
if: github.event.inputs.dry_run == 'true'
run: |
echo "Dry run complete. No branches were deleted."
echo ""
echo "Release branches found:"
git for-each-ref --format '%(refname:strip=3) - %(committerdate:relative)' \
'refs/remotes/origin/release/*' 'refs/remotes/origin/v[0-9]*' 2>/dev/null || echo "None found"
+34
View File
@@ -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.
-1
View File
@@ -1 +0,0 @@
github: [privilegedescalation]
-73
View File
@@ -1,73 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-1
View File
@@ -1 +0,0 @@
# .github
Submodule headlamp-rook-plugin added at 79eaa6910d
Submodule headlamp-sealed-secrets-plugin added at 143b2c36e0
+1
View File
@@ -0,0 +1 @@
test
Submodule
+1
Submodule org added at c420e1543f
-53
View File
@@ -1,53 +0,0 @@
<p align="center">
<img src="privilegedescalation-logo.jpg" alt="Privileged Escalation" width="300" />
</p>
<div align="center">
![GitHub Org stars](https://img.shields.io/github/stars/privilegedescalation)
![GitHub followers](https://img.shields.io/github/followers/privilegedescalation)
![License](https://img.shields.io/github/license/privilegedescalation/.github)
![Profile views](https://komarev.com/ghpvc/?username=privilegedescalation&color=brightgreen)
</div>
<h3 align="center">Headlamp plugins for the infrastructure you actually run.</h3>
<p align="center">
<a href="https://artifacthub.io/packages/search?org=privilegedescalation&kind=21">Artifact Hub</a>
·
<a href="https://headlamp.dev">Headlamp</a>
·
<a href="https://github.com/sponsors/privilegedescalation">Sponsor</a>
</p>
---
We build open source [Headlamp](https://headlamp.dev) plugins that bring deep visibility into Kubernetes storage, networking, GPU, and security subsystems — right inside your cluster dashboard.
## Our Plugins
| Plugin | What it does | Artifact Hub |
|--------|-------------|:---:|
| [headlamp-rook-plugin](https://github.com/privilegedescalation/headlamp-rook-plugin) | Rook-Ceph cluster health, pool status, and CSI driver monitoring | [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/headlamp-rook-plugin)](https://artifacthub.io/packages/headlamp/headlamp-rook-plugin/headlamp-rook-plugin) |
| [headlamp-tns-csi-plugin](https://github.com/privilegedescalation/headlamp-tns-csi-plugin) | TrueNAS CSI driver visibility and kbench storage benchmarking | [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/headlamp-tns-csi-plugin)](https://artifacthub.io/packages/headlamp/headlamp-tns-csi-plugin/headlamp-tns-csi-plugin) |
| [headlamp-sealed-secrets-plugin](https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin) | Manage Bitnami Sealed Secrets with client-side encryption | [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/headlamp-sealed-secrets-plugin)](https://artifacthub.io/packages/headlamp/headlamp-sealed-secrets-plugin/headlamp-sealed-secrets-plugin) |
| [headlamp-polaris-plugin](https://github.com/privilegedescalation/headlamp-polaris-plugin) | Fairwinds Polaris security and best-practices auditing | [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/headlamp-polaris-plugin)](https://artifacthub.io/packages/headlamp/headlamp-polaris-plugin/headlamp-polaris-plugin) |
| [headlamp-intel-gpu-plugin](https://github.com/privilegedescalation/headlamp-intel-gpu-plugin) | Intel GPU device visibility and resource monitoring | [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/headlamp-intel-gpu-plugin)](https://artifacthub.io/packages/headlamp/headlamp-intel-gpu-plugin/headlamp-intel-gpu-plugin) |
| [headlamp-kube-vip-plugin](https://github.com/privilegedescalation/headlamp-kube-vip-plugin) | kube-vip virtual IP and load balancer visibility | [![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/headlamp-kube-vip)](https://artifacthub.io/packages/headlamp/headlamp-kube-vip/headlamp-kube-vip) |
## Why Headlamp?
Headlamp is a CNCF-listed Kubernetes dashboard built for extensibility. Our plugins slot in natively — no separate UIs, no context switching. If you run Headlamp, you can add any of our plugins with a single command.
## Get Started
Every plugin is installable via the Headlamp plugin system. See individual repos for install instructions.
## Contributing
We welcome contributions, bug reports, and feature requests. Open an issue on any repo or start a discussion. All projects are licensed under Apache 2.0.
## Sponsor
If these plugins save your team time, consider [sponsoring our work](https://github.com/sponsors/privilegedescalation). Sponsorship funds go directly toward new plugin development and maintenance.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

-33
View File
@@ -1,33 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"gitAuthor": "Renovate Bot <bot@renovateapp.com>",
"extends": ["config:recommended"],
"baseBranches": ["main"],
"schedule": ["every weekend"],
"prConcurrentLimit": 5,
"pinDigests": true,
"packageRules": [
{
"matchManagers": ["npm"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "npm minor and patch"
},
{
"matchManagers": ["npm"],
"matchUpdateTypes": ["major"],
"groupName": "npm major updates",
"automerge": false
},
{
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"],
"groupName": "github-actions minor and patch"
},
{
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["major"],
"groupName": "github-actions major updates",
"automerge": false
}
]
}
-49
View File
@@ -1,49 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Reads a newline-separated list of changed files from stdin.
# Outputs "pipeline-a" or "pipeline-b" to stdout.
# Pipeline B: all files are infra-only (config, docs, workflows).
# Pipeline A: any non-infra file present.
detect_pipeline() {
local all_infra=true
while IFS= read -r file; do
[ -z "$file" ] && continue
local filename
local dir
filename=$(basename "$file")
dir=$(dirname "$file")
if [[ "$dir" == ".github" || "$dir" == .github/* ]] || \
[[ "$dir" == "infra" || "$dir" == infra/* ]] || \
[[ "$dir" == "org" || "$dir" == org/* ]] || \
[[ "$filename" == *.md ]] || \
[[ "$filename" == .eslintrc* ]] || \
[[ "$filename" == .prettierrc* ]] || \
[[ "$filename" == renovate.json* ]] || \
[[ "$filename" == .gitignore ]] || \
[[ "$filename" == .editorconfig ]] || \
[[ "$filename" == LICENSE ]] || \
[[ "$filename" == Dockerfile ]] || \
[[ "$filename" == docker-compose* ]] || \
[[ "$filename" == Makefile ]]; then
continue
else
all_infra=false
break
fi
done
if [ "$all_infra" = true ]; then
echo "pipeline-b"
else
echo "pipeline-a"
fi
}
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
detect_pipeline
fi
-145
View File
@@ -1,145 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/detect-pipeline.sh"
PASS=0
FAIL=0
assert_eq() {
local test_name="$1" expected="$2" actual="$3"
if [ "$expected" = "$actual" ]; then
echo "PASS: $test_name"
PASS=$((PASS + 1))
else
echo "FAIL: $test_name (expected=$expected, actual=$actual)"
FAIL=$((FAIL + 1))
fi
}
run_detect() {
echo "$1" | detect_pipeline
}
# --- Pipeline B cases (infra-only) ---
assert_eq "single .github root file" "pipeline-b" \
"$(run_detect ".github/dependabot.yml")"
assert_eq ".github/workflows subdirectory" "pipeline-b" \
"$(run_detect ".github/workflows/ci.yaml")"
assert_eq "deeply nested .github path" "pipeline-b" \
"$(run_detect ".github/workflows/reusable/build.yaml")"
assert_eq "markdown file at root" "pipeline-b" \
"$(run_detect "README.md")"
assert_eq "markdown in subdirectory" "pipeline-b" \
"$(run_detect "docs/CONTRIBUTING.md")"
assert_eq "eslintrc config" "pipeline-b" \
"$(run_detect ".eslintrc.json")"
assert_eq "prettierrc config" "pipeline-b" \
"$(run_detect ".prettierrc.yaml")"
assert_eq "renovate config" "pipeline-b" \
"$(run_detect "renovate.json")"
assert_eq "renovate config5" "pipeline-b" \
"$(run_detect "renovate.json5")"
assert_eq "gitignore" "pipeline-b" \
"$(run_detect ".gitignore")"
assert_eq "editorconfig" "pipeline-b" \
"$(run_detect ".editorconfig")"
assert_eq "LICENSE" "pipeline-b" \
"$(run_detect "LICENSE")"
assert_eq "mixed infra files" "pipeline-b" \
"$(run_detect ".github/workflows/ci.yaml
README.md
.eslintrc.json
LICENSE")"
assert_eq "workflow + markdown combo" "pipeline-b" \
"$(run_detect ".github/workflows/detect-pr-pipeline.yaml
.github/workflows/README.md")"
assert_eq "infra root file" "pipeline-b" \
"$(run_detect "infra/helmrelease.yaml")"
assert_eq "infra nested file" "pipeline-b" \
"$(run_detect "infra/clusters/prod/kustomization.yaml")"
assert_eq "org root file" "pipeline-b" \
"$(run_detect "org/CODEOWNERS")"
assert_eq "org nested file" "pipeline-b" \
"$(run_detect "org/policies/branch-protection.json")"
assert_eq "Dockerfile" "pipeline-b" \
"$(run_detect "Dockerfile")"
assert_eq "docker-compose.yaml" "pipeline-b" \
"$(run_detect "docker-compose.yaml")"
assert_eq "docker-compose.override.yml" "pipeline-b" \
"$(run_detect "docker-compose.override.yml")"
assert_eq "Makefile" "pipeline-b" \
"$(run_detect "Makefile")"
assert_eq "mixed infra + org + workflow" "pipeline-b" \
"$(run_detect ".github/workflows/ci.yaml
infra/helmrelease.yaml
org/CODEOWNERS
README.md")"
# --- Pipeline A cases (has non-infra files) ---
assert_eq "plugin source file" "pipeline-a" \
"$(run_detect "headlamp-polaris-plugin/src/index.tsx")"
assert_eq "plugin package.json" "pipeline-a" \
"$(run_detect "headlamp-polaris-plugin/package.json")"
assert_eq "root source file" "pipeline-a" \
"$(run_detect "src/main.ts")"
assert_eq "mixed infra + code" "pipeline-a" \
"$(run_detect ".github/workflows/ci.yaml
headlamp-polaris-plugin/src/index.tsx
README.md")"
assert_eq "single non-infra file" "pipeline-a" \
"$(run_detect "server.js")"
assert_eq "plugin code + infra files" "pipeline-a" \
"$(run_detect "infra/helmrelease.yaml
org/CODEOWNERS
headlamp-polaris-plugin/src/index.tsx")"
# --- Edge cases ---
assert_eq "empty input" "pipeline-b" \
"$(run_detect "")"
assert_eq "root dot file (not in infra list)" "pipeline-a" \
"$(run_detect ".env")"
assert_eq ".github-like but not .github dir" "pipeline-a" \
"$(run_detect ".github-backup/config.yaml")"
# --- Summary ---
echo ""
echo "Results: $PASS passed, $FAIL failed"
if [ "$FAIL" -gt 0 ]; then
exit 1
fi
+54
View File
@@ -0,0 +1,54 @@
---
name: coding-standards
description: >
Coding standards for Privileged Escalation. Covers Headlamp plugin
development workflow, registration API, and shared libraries.
---
# Coding Standards
## Headlamp Plugins
All plugins extend [Headlamp](https://headlamp.dev/docs/latest/development/plugins/getting-started), a Kubernetes dashboard with a plugin system.
- **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
### Plugin Commands
Run from the plugin directory:
| 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 |
### Registration API
Import from `@kinvolk/headlamp-plugin/lib`:
- `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
### K8s API Access
```typescript
import { K8s } from '@kinvolk/headlamp-plugin/lib';
const [pods, error] = K8s.ResourceClasses.Pod.useList();
```
### Shared Libraries
These are provided by Headlamp at runtime — **do not bundle them**:
React, React Router, Redux, MUI, Lodash, Monaco Editor, Notistack, Iconify.
+26
View File
@@ -0,0 +1,26 @@
---
name: safety
description: >
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 Considerations
The following rules apply to all agents at Privileged Escalation without exception.
## 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.
* **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.
* **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.
* **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.
+249
View File
@@ -0,0 +1,249 @@
---
name: sdlc
description: >
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
## GitHub 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.
Token expires after ~1 hour. Re-invoke the skill to regenerate if needed.
## GitHub Issues — Board Approval Required
**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": ["{issue-id}"],
"payload": {
"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` until `PAPERCLIP_APPROVAL_STATUS` confirms approval. Only proceed once approved.
## Branch Strategy
All plugin repositories use a single long-lived branch:
| Branch | Environment | Who merges |
|--------|-------------|------------|
| `main` | Production | CEO (Countess von Containerheim) after triple approval |
**Engineers always target `main` via feature branches** — never push directly.
Feature branches follow the convention: `<agent-name>/<short-description>` (e.g., `gandalf/add-sealed-secrets-list`).
## 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
gh pr create --title "..." --body "... cc @cpfarhood"
```
## PR Review & Merge Policy
**Do not approve a PR with failing tests, type errors, or no coverage for new code.**
Requires **3 approving GitHub reviews** before the CEO merges:
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
**Review order is mandatory: CI → UAT → QA → CTO → CEO merge.** Each stage gates the next. No agent merges their own PRs.
## 48-Hour PR Review SLA (Binding)
**MANDATORY: Every open PR must receive its first review within 48 hours of submission. No exceptions.**
### 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
### 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
### 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
### 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)
### Enforcement Mechanism
CEO creates daily automated report of SLA status and escalates immediately when thresholds breach. This is non-negotiable work.
## Pipeline
**Two pipelines based on change type:**
### Pipeline A: Plugin/Feature Changes (User-Facing Code)
```
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
```
**Applies to:** Changes in `headlamp-*-plugin/` repos (plugin code, features, bug fixes)
### Pipeline B: Infrastructure Changes (No UI Impact)
```
CI: Engineer opens PR → CI runs (lint, types, unit tests)
QA: Regression Regina reviews code and correctness (no E2E needed)
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)
**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.
**Detection:** If `git diff` shows changes only in `.github/`, `infra/`, `org/`, or deployment files → Pipeline B. If any `headlamp-*-plugin/` code changed → Pipeline A.
### Stage 1 — Engineer Opens PR
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.
### Stage 2 — UAT Review (Pipeline A Only)
4. **Pipeline A only (user-facing changes):** Pixel Patty picks up PRs with passing CI.
5. **Pipeline B skips this:** Infrastructure PRs bypass UAT and go directly to QA.
6. Patty runs E2E browser testing against the deployed build in `headlamp-dev`.
7. Pass → hands off to QA. Fail → goes directly to engineer.
### Stage 3 — QA Review
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.
### Stage 4 — CTO Review
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:
### Step 1 — Explicit Assignment
PATCH the issue with `assigneeAgentId: "<target-agent-uuid>"`.
@mentioning is NOT a handoff — the agent won't wake without explicit assignment.
### Step 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.
### 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 checkout the issue.
## Status Semantics
| 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 |
**Never use `in_review` for handoffs.** It does not trigger inbox-lite and the receiving agent will not wake.
## Status Transition Rules
| 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` |
## CI/CD
- 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
## Security Review
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.
## Work Distribution
- 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.