Files
PFVCluster/AGENTS.md
T
mrcharles 95f475c2f9 feat(hooks): add ticket-gate Crush hook — enforce ticket-first work
Mechanically enforces the Agent Authority policy: blocks all modifying
operations (bash, edit, write, multiedit) until an active ticket is set
via: echo '#NNN' > .crush/active-ticket

Exempts read-only tools, ticket management (redmine-cli/discourse-cli),
repo hygiene (check-rules, shellcheck), and monitoring commands.

Also: un-ignore .crush/crush.json so the hook config is tracked in git.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-11 11:56:42 -05:00

25 KiB

Agent Guidelines

Agent Authority (NON-NEGOTIABLE)

No work is permissible on any system without an approved Redmine ticket. There are no exceptions to this rule.

Scope of authority

  1. Tickets govern all work. The agent performs ONLY the work described in the approved Redmine ticket. Anything outside that scope — no matter how small, helpful, or "obvious" — is prohibited.

  2. No autonomous system changes. The agent does not modify, configure, create, delete, or grant anything on a production system unless it is explicitly directed by an approved ticket. "Production system" means every system in the fleet — there is no "test" exception unless the ticket says so.

  3. Propose, never implement. If the agent discovers additional work that should be done — a bug, a misconfiguration, a missing dependency, an enhancement — it does NOT implement it. Instead, it creates a Redmine sub-ticket (status Feedback) describing the finding and surfaces it to the user for approval.

  4. Security and access changes require extra scrutiny. Changes to sudoers, SSH keys, user accounts, firewall rules, authentication policy, file permissions, or any privilege-related configuration are treated as policy decisions, not implementation details. The agent may suggest them but NEVER implements them without explicit user direction in the ticket or a sub-ticket the user has approved.

  5. The user makes policy. The agent implements policy. The agent does not decide who gets sudo, what keys go where, what services run, or what the access model is. The agent executes the user's decisions, exactly as specified.

  6. When in doubt, ask. If the ticket is ambiguous, if a task seems to require something not explicitly authorized, or if the agent is unsure whether an action is in scope — STOP and ask the user via the ticket or directly. Asking is always acceptable. Overstepping is never acceptable.

  7. NEVER close a ticket without explicit user permission. You may SUGGEST a close when the result is clearly scoped and delivered. If it's ambiguous whether the work is truly complete, don't suggest a close — leave that decision to the user. This applies to ALL tickets, no exceptions.

Access-channel policy: SSH only (NON-NEGOTIABLE)

The qemu guest-agent is NEVER an access, execution, or key-delivery channel. SSH (sshd) is the only approved remote access path. Every command must flow through sshd so it is captured by the standard auth/logging/audit infrastructure. This is an ITAR/CMMC/TS/SCI environment — there is no back-door exception, ever.

  • Forbidden: qm guest exec (runs arbitrary commands inside a guest over an unaudited channel) and any wrapper around it (e.g. a vm-guest mode). This includes using guest-agent to deliver an SSH key, even if the resulting SSH login is itself audited — the delivery bypassed audit.
  • Allowed: installing or checking qemu-guest-agent for its intended purpose — letting Proxmox see guest state (qm guest cmd <id> ping, agent: 1 config, apt install qemu-guest-agent). Visibility only; never execution.
  • Enforced mechanically: scripts/check-rules.sh rule #11 fails on any qm guest exec / vm-guest pattern in code. tests/remote.sh has no guest-exec mode.
  • If a system is locked out (no SSH key, no guest-exec path): surface it to the user. Do NOT improvise an alternate back-channel. The user authorizes the unblock method (console login, credential, etc.).

Ticket-closing policy (NON-NEGOTIABLE)

NEVER close a Redmine ticket without explicit user permission. You may SUGGEST a close when the result is clearly scoped and delivered. If it's ambiguous whether the work is truly complete, don't suggest a close — leave that decision to the user. This applies to ALL tickets, no exceptions.

What this means in practice

  • Discovered a typo in a config during approved work? Finish the approved work. Create a sub-ticket for the typo. Do not fix it inline.
  • Think a system should also have localuser sudo configured? Do not add it. Propose it in a sub-ticket.
  • Need to install a package the ticket didn't mention? Ask first.
  • Found a security issue? Create a ticket immediately with full details. Do not remediate without approval.

This environment operates in ITAR/CMMC/TS/SCI space. Every action must be traceable to an approved ticket. There is no "I thought it would help."

Quick Start

You are an AI agent working on this project. Your first actions, in order:

SESSION-START GATE (NON-NEGOTIABLE): Steps 1-3 orient you. Step 4 is the check-for-understanding gate. You MUST NOT begin any task work (steps 5+) until the user confirms your rule summary is correct. This runs every session, automatically — the user should never have to ask for it.

  1. Set up the environment: bash scripts/setup-hooks.sh (installs git hooks — idempotent).
  2. Read this file (AGENTS.md) — project policy and domain knowledge.
  3. Read questions-v1.md — open questions awaiting human input.
  4. Check for understanding — GATE. In your own words, summarize ALL the rules back to the user before doing any work: Agent Authority (ticket-governed, propose-never-implement), Access-Channel SSH-only policy, Questions policy (no harness question-tools), Documentation policy (Discourse is SoR), Redmine tracking, Git policy (always commit+push, shellcheck), Mandatory infra-change documentation (#298 audit log), SSH routing chokepoints (tests/remote.sh), the mechanically enforced check-rules.sh rules, and Credentials (Vault migration TODO). Then stop and wait for the user to confirm. This checkpoint guarantees every session starts aligned.
  5. Check Redminedocker run --rm --env-file ~/.creds/redmine.env git.knownelement.com/reachableceo/redmine-cli:latest list --assigned-to-me -p 55 for active work.
  6. Check current state: git log --oneline -10.
  7. Run rule audit: bash scripts/check-rules.sh --fast.

Enforcement Model

Git hooks (scripts/pre-commit, scripts/pre-push) enforce the rules defined in scripts/check-rules.sh. The rules engine checks: shellcheck (zero warnings including info-level), Docker image pinning (no :latest), container naming, required files, Discourse pointer headers, and more. Run bash scripts/check-rules.sh for a full audit or --fast for pre-commit speed. Bypass with --no-verify (emergencies only).

Task Tracking

  • Redmine is the system of record for all work.
  • NEVER close a ticket without explicit user permission. Suggest a close when clearly scoped/delivered; if ambiguous, don't suggest.
  • Ticket-first enforcement (mechanically enforced). Before starting any work, set the active ticket: echo '#NNN' > .crush/active-ticket. The Crush hook (hooks/ticket-gate.sh) blocks modifying operations until this file exists. If no ticket exists, CREATE ONE FIRST via redmine-cli, then set it. Clear when done: > .crush/active-ticket.
  • WORKING.md is the only in-repo task tracker — a scratchpad for the current session. The pre-commit hook blocks commits while any task remains unchecked.
  • Clear WORKING.md before responding to the user.

Working Style

  • Stop over-thinking. Get to code and output faster. Explore with code; gather ground truth. Do not burn tokens reasoning about things a quick command answers.
  • Farm work out to deterministic tooling: linters, LSPs, formatters, test runners. If an LSP is wired up, use it; otherwise pull a Docker image and lint inside it.
  • Use sub-agents as subcontractors: scoped spec in, distilled deliverable out. Never read 10+ files sequentially; batch into agent calls.

Questions (NON-NEGOTIABLE)

NEVER use a harness "question"/"ask user" tool (structured prompts, modal forms, tabbed questions). Banned across every project, every harness. They are not portable, not version-controlled, and bypass the git record. All questions go in questions-v1.md — write the question; the human edits the answer inline in the same file. Version up per round. Synthesize resolved Q&A into Discourse (decisions) and Redmine (work items). See BASELINE-PROMPT.md §10.

Documentation policy (IMPORTANT)

Discourse is the canonical source of truth for all knowledge documentation. Knowledge docs (architecture, runbooks, references, audits, policies) have been migrated to community.turnsys.com as wiki topics in the VP TechOps category.

All .md files in this repo (except AGENTS.md and LICENSE) are now pointers that link to their corresponding Discourse topic. Do not update documentation content in git — edit the Discourse wiki topic instead. Git edit history no longer serves as the documentation changelog; Discourse preserves wiki edit history automatically.

Code (scripts, configs, playbooks) still lives in git as the source of truth for executables. Only documentation moved to Discourse.

Top-level files

All .md files now point to Discourse. The key pointers:

File Points to Discourse topic
README.md Project overview #296
STATUS.md Ticket index + infra summary #297
docs/docmap.md Documentation index #296

Work tracking stays in Redmine.

Repository Layout

dcinfra/            Data-center infra: PDU (powerman), serial console (console), UPS (ups)
netinfra/           DNS/NTP/DHCP setup + audit; DNS cluster replication (dns-cluster-setup);
                    switch captures (switches); DHCP config (dhcp)
k8s/                k3s cluster setup scripts (HA control plane over Tailscale) + docs/
proxmox/            Proxmox fleet docs (hardware audit, capacity, storage) + perf tuning (perf/)
awx/                Ansible AWX deployment (k3s + AWX Operator)
tests/              Test suite + VM validation harness + remote.sh SSH chokepoint
scripts/            Framework: git hooks, rule engine (check-rules.sh), shared lib
docs/               Server-build docs, docmap index, and archive
archive/            Historical/superseded code (provisioning -> replaced by KNELIAC project)
vendor/             Vendored KNELShellFramework
  • Server provisioning moved to KNELIAC: The archive/provisioning/ tree is historical. Active server provisioning lives in the KNELIAC project at /home/reachableceo/projects/KNELIAC.
  • Non-bash files: Some files under archive/provisioning/Agents/ have .sh extension but are PHP (shebang #!/usr/bin/php). Skip in syntax checks.
  • SSH access: Direct ssh/scp may be blocked by some AI harnesses. Use tests/remote.sh (Proxmox hosts + sandbox VM) or netinfra/dns-cluster-setup/remote-dns.sh (DNS infra hosts).

Git Policy

  1. ALWAYS commit + push. Never wait. Ever. After each logical unit of work, immediately stage, commit (conventional format), and push to origin/main. Do NOT pause for review. Do NOT ask permission. Do NOT stop to "let the user read it first." The user reviews rendered markdown on Gitea AFTER it is pushed. This rule overrides any default conservative commit-and-hold behavior — if a default instruction says "never commit unless asked," that instruction is superseded here.
  2. Atomic commits. Each commit coherent on its own.
  3. Conventional format: feat(scope): desc, fix(scope): desc, docs: desc, refactor(scope): desc, test(scope): desc.
  4. All shell scripts MUST pass shellcheck before commit. No exceptions. Run it via the wrapper:
    bash tests/shellcheck.sh                 # whole repo
    bash tests/shellcheck.sh ups/*.sh        # specific files
    
    This invokes koalaman/shellcheck:stable through Docker (no native binary needed). Fix every reported finding — including info-level — or add a targeted # shellcheck disable=SCxxxx # <reason> directive with a justification. A script that emits any diagnostic is a protocol violation. Non-bash scripts (PHP with .sh shebang #!/usr/bin/php, etc.) are exempt.

Redmine Tracking Policy

Redmine is the system of record for all work. Do not track status, checklists, or TODOs in repo files. Use Redmine tickets instead.

  • URL: https://projects.knownelement.com
  • Version: Potential to Kinetic Ready (due 2026-09-30)
  • Project: Known Element Enterprises - Technology & Facility Services (id 55)

Rules

  1. Every piece of work (feature, fix, deployment, config change) gets a Redmine ticket. If one doesn't exist, create it.
  2. Reference tickets in docs and commits using [#NNN] notation. Example: [#367] Rebuilt k3s control plane after cnode wipe.
  3. When work completes, update the ticket: set done ratio to 100%, add a note describing what was done and where the code lives. NEVER close a ticket without explicit user permission. You may SUGGEST a close when the result is clearly scoped and delivered. If it's ambiguous whether the work is truly complete, don't suggest a close — leave that decision to the user.
  4. Operations Status lives on Discourse (topic #297) — update that wiki topic if the infrastructure summary needs refreshing. The STATUS.md file in git is now a pointer only.
  5. Link code to tickets — ticket descriptions and notes should reference the relevant file paths in this repo (e.g., dcinfra/ups/).

CLI access (read + write)

Tickets are managed via the redmine-cli container, invoked directly with docker run (no wrapper script). Full command reference, patterns, and the subtask escape hatch live in the CLI's own AGENTS.md (~/projects/KNEL-AIMiddleware/tooling-cli/redmine/AGENTS.md) — read it for anything beyond the basics.

# Connection sanity check (run first in any session):
docker run --rm --env-file ~/.creds/redmine.env \
    git.knownelement.com/reachableceo/redmine-cli:latest whoami

# Your queue (project 55):
docker run --rm --env-file ~/.creds/redmine.env \
    git.knownelement.com/reachableceo/redmine-cli:latest list --assigned-to-me -p 55

# Show / create / update / close:
docker run --rm --env-file ~/.creds/redmine.env \
    git.knownelement.com/reachableceo/redmine-cli:latest show 367
docker run --rm --env-file ~/.creds/redmine.env \
    git.knownelement.com/reachableceo/redmine-cli:latest create -p 55 -s "Subject" -d "desc"
docker run --rm --env-file ~/.creds/redmine.env \
    git.knownelement.com/reachableceo/redmine-cli:latest update 367 -n "Done: committed in abc123" --done-ratio 100
docker run --rm --env-file ~/.creds/redmine.env \
    git.knownelement.com/reachableceo/redmine-cli:latest close 367

Key IDs: project 55 (technicaloperations), user 5 (reachableceo). Statuses: New(1), In Progress(2), Resolved(3,closed), Feedback(4), Closed(5), Rejected(6). New subtasks go to Feedback (4). Tracker 3 = Support. Gotcha: create has no --parent flag — to make a subtask, use the python-redmine escape hatch inside the container (see tooling-cli/redmine/AGENTS.md). Always show a ticket before updating it.

Credentials (REDMINE_URL/REDMINE_API_KEY) live in the centralized store at ~/.creds/redmine.env.

Documentation Workflow

Discourse is the source of truth for all knowledge docs. After any work session, an agent MUST:

  1. Update Discourse wiki topics — if infrastructure facts changed (new VM, IP change, host retired), edit the relevant wiki topic at community.turnsys.com/c/vp-techops.
  2. Update the Operations Status topic (#297) if tickets were opened or closed.
  3. Grep for stale paths in codegrep -rn 'old/path' --include='*.sh' after any rename or restructure. Fix all references in the same commit.
  4. If a new top-level directory was created, update:
    • AGENTS.md → "Repository Layout" code block
    • AGENTS.md → "Key Scripts" table (if it has an entrypoint script)
    • Create a new Discourse wiki topic for any documentation
  5. Self-audit before commit. Code changes must be internally consistent. Documentation changes go to Discourse, not git.

CLI access (read + write)

Wiki topics are managed via the discourse-cli container, invoked directly with docker run (no wrapper script). Full command reference, patterns, and the raw-API escape hatch live in the CLI's own AGENTS.md (~/projects/KNEL-AIMiddleware/tooling-cli/discourse/AGENTS.md) — read it for anything beyond the basics.

# Connection sanity check (run first in any session):
docker run --rm --env-file ~/.creds/discourse.env \
    git.knownelement.com/reachableceo/discourse-cli:latest whoami

# List VP TechOps topics:
docker run --rm --env-file ~/.creds/discourse.env \
    git.knownelement.com/reachableceo/discourse-cli:latest ls -c vp-techops

# Show a topic / edit a wiki post (find post id via `show`):
docker run --rm --env-file ~/.creds/discourse.env \
    git.knownelement.com/reachableceo/discourse-cli:latest show 297
docker run --rm --env-file ~/.creds/discourse.env \
    git.knownelement.com/reachableceo/discourse-cli:latest update <post_id> -b "new markdown body"

VP TechOps = category 74. Key topics: #296 (project overview), #297 (ops status), #298 (audit log). The API user is trust-level 4 but not admin — admin-only ops (category creation, setting the wiki flag) will 403; surface those to the user rather than retrying. Gotcha: update/delete take a post id, not a post number. Never create a new topic for an update to existing knowledge — edit the wiki post in place.

Credentials (DISCOURSE_URL/DISCOURSE_API_KEY/DISCOURSE_API_USERNAME) live in the centralized store at ~/.creds/discourse.env.

Key Scripts

Script Purpose
scripts/check-rules.sh Rule audit engine (shellcheck, image pinning, Discourse pointers, required files)
scripts/setup-hooks.sh Install git hooks (pre-commit, pre-push)
tests/remote.sh SSH chokepoint — all Proxmox host + sandbox VM access routes here
netinfra/dns-cluster-setup/remote-dns.sh SSH chokepoint for DNS infra hosts (netinfra-01/02, tsrouter, netboot)
redmine-cli container Redmine CLI (ticket read/write via docker run; see tooling-cli/redmine/)
discourse-cli container Discourse CLI (wiki topic read/write via docker run; see tooling-cli/discourse/)
dns-cli container Technitium DNS CLI (zones, list, add, delete, search, flush; see tooling-cli/dns/)
tests/vm-validation.sh Deploy + validate on sandbox VM
tests/run-tests.sh Test suite
netinfra/dns-cluster-setup/setup.sh DNS cluster replication
k8s/install-cp.sh Bootstrap k3s HA control plane
dcinfra/powerman/setup.sh Configure Cyclades PDU via powerman
dcinfra/console/setup.sh Configure serial console access via ser2net + conman
dcinfra/ups/setup.sh Configure NUT (Network UPS Tools) for UPS monitoring
proxmox/perf/deploy-tuning.sh Deploy perf tunings
proxmox/perf/scripts/ Read-only audit: probe-storage, probe-network, conman-console, snmp-switch-audit, probe-drift, audit-vm-disks, audit-guest-io, deploy-tuned-guests

Switch Console Access (conman)

Switch configs are pulled via serial console through a conman + ser2net stack on pfv-tsys4.

  • conmand server: pfv-tsys4 (Tailscale 100.70.77.93, port 7890)
  • ser2net: pfv-tsys4, TCP ports 2001-2006 on Tailscale IP (do NOT connect to ser2net directly — it conflicts with conman's persistent sessions; always use the conman client)
  • Script: proxmox/perf/scripts/conman-console.py — drives console sessions read-only via PTY. No expect/tcl required.
  • Command files: netinfra/switches/.cmds files with switch-specific show commands
  • Query available consoles: conman -d 100.70.77.93 -q
# Pull a switch config (example):
CONMAN_SERVER=100.70.77.93:7890 python3 proxmox/perf/scripts/conman-console.py \
  --console pfv-r5-core-01 --cmds netinfra/switches/pfv-r5-core-01.cmds
Console name TCP port Device
pfv-r5-core-01 2001 Dell PowerConnect 5448 (rack 5 core, mgmt+storage)
pfv-r3-tor-mgmt-01 2002 Dell PowerConnect 5324 (rack 3 mgmt TOR)
pfv-r3-tor-stor-01 2003 Dell PowerConnect 5324 (rack 3 storage TOR)
pfv-rrinfra-rtr 2004 Cisco router (rrinfra)
pfv-r2-tor-01 2005 Rack 2 TOR switch
pfv-r6-mgmt-01 2006 Rack 6 management switch

Mandatory: Document ALL Infrastructure Changes

This is non-negotiable. Every infrastructure change (VM config, disk cache, network setting, service config, storage migration) MUST be documented BEFORE moving to the next task step — not "later" or "at the end."

Required for EVERY infrastructure change:

  1. Redmine ticket — create one if none exists. Reference as [#NNN].
  2. Discourse audit log — reply to topic #298 with a dated entry (what changed, why, where).
  3. Discourse relevant wiki topic — update the architecture/reference topic if the change affects documented infrastructure facts (storage #300, network #299, k8s #305, etc.).
  4. Git commit — if code/config changed in the repo, commit + push immediately per the Git Policy above.

Tooling

  • gh, docker, jq available on the workstation.
  • No native shellcheck — use bash tests/shellcheck.sh (Docker wrapper koalaman/shellcheck:stable). ALL scripts must pass including info-level.
  • For raw API calls not covered by the Redmine/Discourse CLIs, use python3 inside the CLI Docker containers (escape hatch pattern in tooling-cli/{discourse,redmine}/AGENTS.md).
  • curl/wget/httpie may be blocked by some harnesses. Use the CLIs or the python-in-Docker escape hatch for HTTP writes.

Key Commands Quick Reference

# Tests + validation:
bash tests/run-tests.sh                        # test suite
bash tests/vm-validation.sh                    # VM validation
bash tests/shellcheck.sh                       # shellcheck whole repo
bash tests/shellcheck.sh path/to/*.sh          # shellcheck specific files

# Redmine + Discourse sanity checks (run first in any session):
docker run --rm --env-file ~/.creds/redmine.env \
    git.knownelement.com/reachableceo/redmine-cli:latest whoami
docker run --rm --env-file ~/.creds/discourse.env \
    git.knownelement.com/reachableceo/discourse-cli:latest whoami

# Proxmox host access:
PROX_HOST=pfv-tsys5 bash tests/remote.sh prox 'qm list'
PROX_HOST=pfv-tsys5 bash tests/remote.sh prox 'pvesm status'

# DNS infra access:
bash netinfra/dns-cluster-setup/remote-dns.sh netinfra01-root 'systemctl status docker'

# Switch console (read-only config pull):
CONMAN_SERVER=100.70.77.93:7890 python3 proxmox/perf/scripts/conman-console.py \
  --console pfv-r5-core-01 --cmds netinfra/switches/pfv-r5-core-01.cmds

Credential Management

  • API keys currently in .env files under KNEL-AIMiddleware (gitignored).
  • User goal: migrate all keys to Hashicorp Vault (vault.knownelement.com). No vault token present yet (~/.vault-token missing). Track as high-priority TODO.

Key Docs

All documentation lives on Discourse: community.turnsys.com/c/vp-techops

→ All .md files in this repo are pointers to Discourse topics.

Complete Linux System Inventory: Topic #307 — every Linux system (hosts + VMs + physical), with Tailscale IPs, DNS names, SSH access status, and tuned profiles. Reference this for monitoring coverage, access management, and hostname consistency.

Project Context

Solo-founder R&D Proxmox cluster in a private residence. Shoestring budget. Production lives on a Cloudron VPS in Reston VA. See the Operations Status topic for the ticket index and infrastructure summary. All work is tracked in Redmine (version: Potential to Kinetic Ready, due 2026-09-30).