Added scripts/fix-swtpm-permissions.sh that sets default ACLs on /var/lib/libvirt/swtpm/ so new per-VM state directories inherit libvirt-qemu access. This permanently fixes the "CMD_INIT: 0x9" error caused by libvirtd creating swtpm dirs as root:root. The user runs this ONCE with sudo. ACLs persist across reboots and apply to all new VMs automatically. Updated vm_create error message to reference the fix script. Updated AGENTS.md with corrected swtpm setup instructions. All 523 tests pass, 0 lint warnings. 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
20 KiB
KNEL-Football Secure OS - Agent Behavior Guidelines
Quick Start
You are an AI agent (Crush) working on this project.
Your First Actions (MANDATORY)
- Read STATUS.md - Check current project status (build state, blockers, next actions)
- Read docs/SDLC.md - CRITICAL: Understand the MANDATORY development workflow
- Read docs/PRD.md - Understand requirements (source of truth)
- Check current state:
ls -lh output/andgit log --oneline -10
Use Sub-Agents Liberally (MANDATORY)
You MUST use the agent tool to parallelize work and manage context.
- Audit/review tasks: Dispatch multiple agents in parallel to read different files, test suites, or doc sets simultaneously
- Context management: Use agents for large file reads to keep main context lean. Agents can read, search, and analyze without bloating your context window
- Never read 10+ files sequentially: Batch them into 2-3 agent calls instead
- Pattern: Read 3-4 files yourself for immediate edits, dispatch agents for everything else
Good patterns:
# Parallel audit of different subsystems:
agent("Read all hooks in config/hooks/live/ and report issues")
agent("Read all test files and count tests, find stale ones")
agent("Read all docs and check PRD alignment")
# Context management for large files:
agent("Read src/security-hardening.sh and report all functions and issues")
⚠️ CRITICAL RULES - READ THESE FIRST
1. AUTO-COMMIT & AUTO-PUSH IS MANDATORY
You MUST commit and push AUTOMATICALLY as you work. NEVER wait for user to ask.
- Commit after EVERY logical change - Don't batch work
- Push immediately after commit -
git push origin main - Never ask permission to commit - Just do it
- Never leave uncommitted changes - At session end, everything is committed
2. SDLC COMPLIANCE IS MANDATORY
You MUST follow docs/SDLC.md for EVERY change. NO EXCEPTIONS.
The SDLC defines a MANDATORY workflow that you MUST follow:
┌─────────────────────────────────────────────────────────────────────┐
│ MANDATORY SDLC WORKFLOW │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. READ SDLC.md FIRST - Before starting ANY work │
│ └─ This is NOT optional. Read it. Every time. │
│ │
│ 2. WRITE TESTS FIRST (TDD) │
│ └─ RED: Write failing test BEFORE implementation │
│ └─ Tests MUST exist before you write ANY code │
│ │
│ 3. IMPLEMENT CODE │
│ └─ GREEN: Write minimal code to pass the test │
│ │
│ 4. UPDATE DOCUMENTATION │
│ └─ PRD.md - Add/update requirements │
│ └─ security-model.md - Update architecture │
│ └─ TEST-COVERAGE.md - Document new tests │
│ │
│ 5. RUN ALL TESTS │
│ └─ ./run.sh test MUST pass │
│ └─ ./run.sh lint MUST pass with zero warnings │
│ │
│ 6. COMMIT │
│ └─ Pre-commit hook will verify all checks pass │
│ │
│ 7. PUSH │
│ └─ Changes are not complete until pushed │
│ │
└─────────────────────────────────────────────────────────────────────┘
Pre-Commit Hook (Automatic Enforcement)
A pre-commit hook automatically enforces SDLC requirements:
- Runs
./run.sh lint- Blocks commit on any warnings - Runs
./run.sh test:unit- Blocks commit on test failures - Checks test coverage - Blocks commit if tests missing for modified code
- Warns on missing docs - Reminds to update documentation
The hook is a SAFETY NET, not a substitute for following the process.
Violations That Will Get You Blocked
| Violation | Consequence |
|---|---|
| Not reading SDLC.md first | Pre-commit hook will fail |
| Writing code before tests | Pre-commit hook will fail |
| Missing test files | Pre-commit hook will fail |
| Lint warnings | Pre-commit hook will fail |
| Test failures | Pre-commit hook will fail |
| Missing documentation updates | Pre-commit warning |
Where to Find Things
| Need | File |
|---|---|
| DEVELOPMENT WORKFLOW | docs/SDLC.md (READ FIRST) |
| Current status (build state, blockers) | STATUS.md |
| Requirements (source of truth) | docs/PRD.md |
| Test coverage details | docs/TEST-COVERAGE.md |
| Verification/compliance | docs/VERIFICATION-REPORT.md |
| Security architecture | docs/security-model.md |
| AI memory/ADRs | JOURNAL.md |
Project Structure
/
├── run.sh # MAIN ENTRY POINT - All operations
├── Dockerfile # Multi-stage build environment
├── README.md # Project overview
├── AGENTS.md # THIS FILE - Agent guidelines
├── STATUS.md # Current status (maintained by AI)
├── JOURNAL.md # AI memory - ADRs, lessons (append-only)
└── docs/
├── SDLC.md # ⚠️ MANDATORY WORKFLOW - READ FIRST
├── PRD.md # Product Requirements (source of truth)
├── TEST-COVERAGE.md # Test suite details
├── VERIFICATION-REPORT.md
├── COMPLIANCE.md
└── security-model.md
src/ # Source scripts
scripts/ # Utility scripts (setup-githooks.sh)
githooks/ # Shared git hooks (pre-commit)
config/ # Configuration files
├── includes.installer/ # Installer configs (preseed.cfg)
├── hooks/live/ # Live system hooks
├── hooks/installed/ # Post-install hooks
└── package-lists/ # Package lists
tests/ # Test suite (BATS framework)
output/ # Build artifacts
AGENT WORKFLOW (MANDATORY)
1. Start Up
# Configure git hooks (if not already done)
./scripts/setup-githooks.sh
# Check current state
ls -lh output/
git log --oneline -10
2. Read SDLC.md (MANDATORY FIRST STEP)
cat docs/SDLC.md
3. Understand Requirements
- Read docs/SDLC.md for MANDATORY development workflow
- Read docs/PRD.md (source of truth)
- Check Mandatory Security Requirements section below
4. Write Tests FIRST (TDD - MANDATORY)
# Create test file BEFORE implementing
vim tests/unit/my_feature_test.bats
# Run test to confirm it FAILS (RED phase)
./run.sh test:unit
5. Implement Code
- Read files before editing (Critical!)
- Use exact text matching (whitespace matters)
- Write minimal code to pass tests (GREEN phase)
6. Update Documentation (MANDATORY)
- Update docs/PRD.md if adding/changing requirements
- Update docs/security-model.md if changing security architecture
- Update docs/TEST-COVERAGE.md with new test counts
- Update JOURNAL.md with ADRs, lessons learned, session notes (append-only)
7. Run Tests
./run.sh lint # MUST pass with zero warnings
./run.sh test:unit # MUST pass
./run.sh test # MUST pass (all tests)
8. Commit (Pre-commit Hook Will Verify)
git status
git diff
git add <files>
git commit -m "type: subject
body (optional)
💘 Generated with Crush
Assisted-by: GLM-4.7 via Crush <crush@charm.land>
"
# Pre-commit hook runs automatically and verifies SDLC compliance
9. Push
git push origin main
MANDATORY SECURITY REQUIREMENTS
Full Disk Encryption (FDE)
Requirement: ALL systems MUST use LUKS2 encryption
- Cipher: AES-256-XTS (512-bit key)
- Format: LUKS2 with Argon2id KDF
- Passphrase: 14+ chars, mixed case, digit, special char
- Implementation:
config/includes.installer/preseed.cfg,config/hooks/installed/encryption-*.sh
Password Complexity
Requirement: ALL passwords MUST meet strict complexity
- Minimum: 14 characters
- Classes: 3 of 4 (upper, lower, digit, special)
- Enforcement: PAM pwquality module
- Implementation:
src/security-hardening.sh,config/hooks/live/security-hardening.sh
Host System FDE
Requirement: Build/test host MUST have FDE enabled
./run.sh isowill FAIL if host FDE not detected./run.sh test:isowill FAIL if host FDE not detected- Detection: checks for LUKS devices,
/etc/crypttab, dm-crypt
DOCKER-ONLY WORKFLOW
Why Docker?
- Reproducible builds
- Isolated environment
- No host system pollution
Volumes
Container Host Purpose
/workspace ./ Project root (read-only)
/build ./tmp Build intermediates
/output ./output Final artifacts
Commands Inside Container
./run.sh build- Build Docker image./run.sh test- Run all tests./run.sh lint- Run linting./run.sh iso- Build ISO
Commands on Host
./run.sh test:iso- Test ISO with libvirt
NEVER
- Create directories in /home
- Install packages on host
- Modify host system files
- Run live-build commands on host
Important Rules
AUTO-COMMIT & AUTO-PUSH (CRITICAL)
You MUST commit and push AUTOMATICALLY as you work. NEVER wait for user to ask.
Commit Frequency
- Commit early and often - After EACH logical unit of work
- One atomic commit per change - Never batch unrelated changes
- Push immediately after commit - Changes are not complete until pushed
When to Commit
- After writing a failing test (TDD: RED phase)
- After making the test pass (TDD: GREEN phase)
- After refactoring code
- After updating documentation
- After fixing a bug
- After ANY meaningful change
Atomic Commits
- Each commit should represent ONE logical change
- If you changed 3 files for one feature → ONE commit
- If you fixed a bug AND updated docs → ONE commit
- If you added a feature AND fixed an unrelated bug → TWO commits
Conventional Commit Format (MANDATORY)
<type>: <subject>
[body - explain WHAT changed, WHY, and context]
[footer - references, breaking changes]
💘 Generated with Crush
Assisted-by: <AI-Model> via Crush <crush@charm.land>
Verbose Commit Messages (MANDATORY)
The body MUST explain:
- WHAT changed (brief summary)
- WHY it changed (context/motivation)
- HOW it works (if non-obvious)
- Any references (PRD requirements, issue numbers)
Example:
security: enforce JOURNAL.md updates in SDLC workflow
JOURNAL.md is the AI memory file containing ADRs and lessons learned.
It was not being consistently updated during development work.
Changes:
- AGENTS.md: Added JOURNAL.md to mandatory documentation step
- SDLC.md: Added JOURNAL.md to documentation sync requirements
- pre-commit hook: Check for JOURNAL.md updates on new functions
Reference: docs/SDLC.md section 4 (Documentation-Code-Test Sync)
💘 Generated with Crush
Assisted-by: GLM-4.7 via Crush <crush@charm.land>
The Commit-Push Cycle
1. Make a logical change (code, test, or docs)
2. Run: ./run.sh lint && ./run.sh test:unit
3. git add <specific-files-for-this-change>
4. git commit with verbose conventional message
5. git push origin main
6. Continue working
NEVER:
- Wait for user to ask you to commit
- Batch multiple unrelated changes into one commit
- Skip the push step
- Leave changes uncommitted at end of session
DO
- Read docs/SDLC.md FIRST before starting ANY work
- Write tests FIRST (TDD is MANDATORY)
- Read files before editing
- Use exact text matching (whitespace matters)
- Test after every change
- Run full test suite before committing
- Double-check
git statusbefore ANY commit - Delete unused/obsolete files when refactoring
- Update documentation when changing behavior
- Follow existing code style
DO NOT
- Skip reading SDLC.md - This is MANDATORY
- Write code before tests - TDD is MANDATORY
- Commit without running tests - Pre-commit will block you
- Edit files you haven't read
- Guess at text matches
- Skip the test suite
- Break existing tests
- Ignore lint errors
- Make unrelated changes in one commit
- Modify host system directly
- Run destructive git operations without explicit instruction
- Amend commits without explicit approval
Commit Message Format
Conventional Commits with Verbose Body (MANDATORY)
<type>: <subject>
<body explaining WHAT, WHY, and context>
<footer if needed>
💘 Generated with Crush
Assisted-by: <AI-Model> via Crush <crush@charm.land>
Types: feat, fix, security, docs, test, refactor, chore
Rules:
- Subject: 50 chars max, imperative mood, no period
- Body: REQUIRED for non-trivial changes, explain context and rationale
- Footer: Reference issues, PRD requirements, breaking changes
- Always include Crush attribution
Atomic Commits:
- One commit = one logical change
- If touching multiple files for one feature → one commit
- If doing unrelated work → separate commits
Error Handling
Build Failures
- Check
/tmp/knel-iso-build.log - Check disk space
- Verify Docker permissions
Test Failures
- Run tests individually:
bats tests/unit/file.bats - Review error messages carefully
Permission Errors
- Ensure
run.shis executable - Check Docker daemon is running
- Verify user in docker group
Success Criteria
- Read docs/SDLC.md first (MANDATORY)
- Tests written first (TDD mandatory)
- All tests pass (
./run.sh test) - Lint passes (
./run.sh lint) - Documentation updated (PRD, security-model, TEST-COVERAGE, JOURNAL)
- AUTO-COMMITED with verbose conventional message
- AUTO-PUSHED to origin main
- No security requirements violated
- Docker workflow followed
- NO UNCOMMITTED CHANGES REMAIN
Remember: This is a security-critical project. SDLC compliance is MANDATORY. Test everything. Read before editing. Follow the workflow. Read docs/SDLC.md FIRST.
For current status, see STATUS.md.
Last Updated: 2026-02-19 SDLC Enforcement: Pre-commit hook + mandatory workflow documentation
File Editing Requirements
Use Linux Command-Line Tools (MANDATORY)
When editing files, prefer Linux command-line tools over internal editing functions.
Preferred Tools:
sed- Stream editor for text transformationsawk- Pattern scanning and processinggrep- Search and filter textpatch- Apply diff filescut- Remove sections from linestr- Translate/delete charactershead/tail- Output first/last linessort/uniq- Sort and deduplicatexargs- Build command lines from input
When to Use Each:
# Replace text in file
sed -i 's/old/new/g' file.txt
# Replace on specific line
sed -i '42s/old/new/' file.txt
# Append after line matching pattern
sed -i '/pattern/a\new line' file.txt
# Delete lines matching pattern
sed -i '/pattern/d' file.txt
# Extract specific column
awk '{print $2}' file.txt
# Process based on condition
awk '/pattern/ {print $1, $3}' file.txt
# Search and replace with regex
sed -i -E 's/pattern/replacement/g' file.txt
# Apply a patch
patch -p1 < changes.diff
Why This Matters:
- Internal editing tools fail frequently with whitespace/encoding issues
- Command-line tools are deterministic and well-tested
- Easier to verify changes before applying
- Better error messages when something goes wrong
- Can preview changes with
sed 's/old/new/g' file(no -i) first
VM Testing & swtpm
VM Creation via ./run.sh test:iso create
The vm_create() function in run.sh handles TPM gracefully:
- If
/var/lib/libvirt/swtpm/exists and is writable: TPM 2.0 emulation is enabled - If not accessible: VM is created WITHOUT TPM with clear warnings
- TPM is required for Secure Boot and disk encryption testing, but NOT required for live ISO boot testing
One-Time swtpm Setup (required for TPM/disk encryption)
Libvirt's swtpm helper creates per-VM state dirs as root:root, but swtpm runs as libvirt-qemu and can't write to them. A permanent fix using default ACLs is provided:
sudo bash scripts/fix-swtpm-permissions.sh
This sets default ACLs on /var/lib/libvirt/swtpm/ so new subdirectories
inherit libvirt-qemu access. Run once - survives reboots and new VMs.
After this, ./run.sh test:iso create will work with TPM enabled.
VM Lifecycle
./run.sh test:iso create # Create and start VM (shows in virt-manager)
./run.sh test:iso status # Check VM status
./run.sh test:iso console # Serial console
./run.sh test:iso destroy # Destroy VM (ISO preserved in output/)
Direct QEMU (alternative when libvirt has issues)
If libvirt swtpm is broken, you can boot the ISO directly:
# Setup swtpm manually
mkdir -p /tmp/swtpm-state
swtpm_setup --tpm-state /tmp/swtpm-state --tpm2 --createek --allow-signing --pcr-banks sha256
swtpm socket --tpmstate dir=/tmp/swtpm-state --tpm2 \
--ctrl type=unixio,path=/tmp/swtpm-sock --daemon --flags not-need-init
# Boot with QEMU
qemu-system-x86_64 \
-machine q35,smm=on -accel kvm -cpu host -smp 2 -m 4096 \
-drive if=pflash,format=raw,unit=0,file=/usr/share/OVMF/OVMF_CODE_4M.secboot.fd,readonly=on \
-drive if=pflash,format=raw,unit=1,file=/tmp/ovmf-vars.fd \
-drive file=/tmp/disk.qcow2,format=qcow2,if=virtio \
-cdrom output/knel-football-secure.iso -boot d \
-netdev user,id=net0 -device virtio-net-pci,netdev=net0 \
-vnc :5 -device virtio-gpu-pci \
-chardev socket,id=chrtpm,path=/tmp/swtpm-sock \
-tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0
Key Lesson: swtpm Must Be Pre-Initialized
swtpm's CMD_INIT fails if the TPM state hasn't been set up with swtpm_setup first.
For libvirt integration, this means /var/lib/libvirt/swtpm/<vm-name>/ must exist
with initialized state and correct ownership (libvirt-qemu:libvirt-qemu).
Session Lessons & Hard-Won Knowledge
DO NOT Delete the ISO in vm_destroy
The ISO takes 7+ minutes to build. vm_destroy() must NEVER delete files from output/.
Only clean up /tmp/ files (disks, copies, XML).
DO NOT Remove TPM From Templates
TPM is required for UEFI Secure Boot and disk encryption. The template must support
conditional TPM via @TPM_SECTION@ placeholder, not have TPM removed entirely.
Always Use PID-Suffixed Paths in /tmp
Previous VM runs may leave files owned by libvirt-qemu that the current user can't
delete. Use /tmp/${VM_NAME}-$$.ext to avoid conflicts.
Test End-to-End, Not Just Components
A passing validation harness does NOT mean the ISO actually boots. Always:
- Boot in QEMU with serial capture
- Check for kernel panics, hung tasks, failed services
- Verify login prompt appears
- Capture and analyze full serial output