Skip to content

Latest commit

 

History

History
494 lines (411 loc) · 33.7 KB

AGENTS.md

File metadata and controls

494 lines (411 loc) · 33.7 KB

Capsem Agent Instructions

The one contract for every coding agent in this repository. CLAUDE.md and GEMINI.md are symlinks to this file, and tests/citadel/test_agent_contract_is_one_file.py keeps them that way.

They used to be separate files with almost disjoint content, which meant each agent was held to a different contract: Claude never saw the release rules or the bounded-diagnostics wrapper, and Codex never saw the code style or the invariants. Nobody chose that -- the files simply grew apart, and nothing was comparing them.

Quick Start

Prioritize implementation. During routine development run only the smallest TDD regression and checks needed for the changed behavior. Do not run broad workspace, contract, VM, install, or benchmark suites after every edit or commit. Qualify the complete result at the end of each major phase and before merge or release. Keep implementation progress separate from qualification status; pending broad proof must not prevent independent coding on later phases.

just doctor        # Check tools (first time)
just doctor fix    # Install prerequisites and materialize missing VM assets
just shell         # Build + boot VM (~10s)
just fast-test     # Incomplete source feedback; prints the next supported rails
just focus-test functional # Rerun one named functional owner
source_commit=$(git rev-parse HEAD)
just test "$source_commit" # Required before merge and release; exact repeats reuse its journal.

# Optional hands-on local testing; never a release prerequisite
just install

# Release only the exact commit whose `just test` passed (see Releases below)
just release-binaries nightly "$source_commit"
just release-assets nightly "$source_commit"

See /dev-just for the full recipe reference and dependency chains.

Project Layout

crates/capsem-foundation/      Low-level paths, UDS, logging, polling, and IPC handshake
crates/capsem-cache/           Bounded cache primitives, freshness publication, and cache telemetry
crates/capsem-crypto/          Accelerated hashes, randomness, TLS keys, JWT verification, secrets
crates/capsem-archive/  Block-compressed body archive for session ledgers (pure Rust)
crates/capsem-ledger/           Confined session-ledger worker and archive codec executor
crates/capsem-telemetry/       Every metrics-facade metric name, kind, unit, and description
crates/capsem-assets/          Asset manifest compatibility, resolution, download, and verification
crates/capsem-api/             Gateway wire types and OpenAPI contract shared by clients
sdk/rust/                     capsem-sdk: async HTTP gateway clients sharing capsem-api DTOs
crates/capsem-config/          Product config types, parsing, validation, and provider/MCP identity
crates/capsem-credentials/     Credential provider contracts and durable credential store
crates/capsem-core/            VM, hypervisor, security engine, and host network runtime
crates/capsem-service/         Daemon service (axum HTTP over UDS, VM lifecycle)
crates/capsem-process/         Per-VM process (boots VM, bridges vsock, job store)
crates/capsem-proxy/           Confined HTTP/DNS proxy worker for VM and standalone traffic
crates/capsem/                 CLI client (create, shell, exec, list, install, assets, update)
crates/capsem-tui/             Terminal control UI (reads and drives state via the gateway)
crates/capsem-admin/           Runtime image/asset/release administration (validate, materialize, publish)
crates/capsem-gateway/         TCP-to-UDS HTTP gateway (frontend + tray + remote auth)
mcp/typescript/                SDK-backed npm host MCP server for AI agents (stdio, authenticated gateway HTTP)
crates/capsem-router/     Confined TCP publication companion (data descriptors only)
crates/capsem-network/    Host end of a guest tun0: smoltcp over a framed VSOCK packet stream
crates/capsem-mcp-transport/   Connected HTTP capabilities and native MCP role bootstrap
crates/capsem-mcp-aggregator/  Low-privilege subprocess: connects to external MCP servers
crates/capsem-mcp-builtin/     Stdio MCP server for built-in tools (HTTP)
crates/capsem-agent/           Guest PTY agent + net-proxy + dns-proxy + mcp-server + sysutil + tun pump (musl)
crates/capsem-app/             Thin Tauri desktop shell (points at gateway)
crates/capsem-tray/            System tray (polls gateway, quick actions)
crates/capsem-proto/           Shared protocol types (host-guest, service-process IPC)
crates/capsem-logger/          Session DB schema, queries, async writer
crates/capsem-guard/           Companion lifecycle primitives (parent-watch + flock singleton)
crates/capsem-bench/           Benchmark harness, ships as capsem-bench-rs (guest musl + host)
crates/capsem-mock-server/     Hermetic mock upstream (HTTP/TLS/WS) for tests and benchmarks
web/app/                 Astro 7 + Svelte 5 + Tailwind v4 + owned semantic CSS
web/marketing/                     Marketing website (Astro + Svelte 5)
web/docs/                     Documentation site (Astro Starlight)
build_system/builder/      capsem-builder backend and gate implementation
build_system/release_site/ Release channel site generator (Astro, writes cache/target/release/distribution/)
build_system/scripts/      Thin functional command boundaries for build and release tooling
config/                   Runtime product config source -- never developer skills (see Skills)
config/docker/image/      VM runtime build inputs (runtime_apt_packages, kernel and rootfs config)
images/                   Application OCI images and their catalog (catalog.toml); applications are not part of the VM runtime
guest/artifacts/          Guest scripts and diagnostics (capsem-init, bashrc, tests)
cache/target/assets/            Built VM assets (gitignored, per-arch: cache/target/assets/{arch}/)
web/graphics/             Brand icons and Tauri app icons (source of truth)
skills/                   Shared AI agent skills (SKILL.md format)
tests/                    Cross-crate suites (ironbank/ black-box gates, citadel/ guards)
                          citadel/ is source-level and runs in the fast phase: a
                          recorded mistake must fail before the expensive work, and
                          each guard carries the reason in its failure message

Read the Gate Digest First

cache/target/gate-runs/DIGEST.md is the state of the build across recent runs: what the last run did, which steps keep failing, where the time goes, and what to do about it. Every gate run regenerates it; uv run --project build_system --frozen capsem-gate runs digest rebuilds it on demand and uv run --project build_system --frozen capsem-gate runs trend --step <label> follows one step run by run.

Read it before starting work and before reporting that anything passes. One green run says nothing about a step that fails one time in four, and an intermittent failure is the most expensive kind here precisely because each sighting looks like bad luck.

Skills

Skills live in skills/ at the project root. This is the canonical checked-in developer skill library. Agent-specific discovery may symlink or copy from this path; runtime product config must not mirror developer skills under config/.

skills/<name>/SKILL.md    One skill per directory

Prefix-based grouping: dev-*, build-*, release-*, site-*, frontend-*, meta-*. asset-pipeline covers the build-to-boot asset flow. See /meta-organize-skills for conventions.

Do not put skill source files in .claude/, .codex/, .gemini/, or config/skills/. Those roots are agent-local settings or product config, not the developer skill source.

Before code changes, load the relevant project skill from skills/. For tests and release gates, load /dev-testing and /ironbank. For debugging, load /dev-debugging. For architecture changes, load /site-architecture.

Skills -- LOAD BEFORE CODING

Skills contain hard-won lessons and project-specific patterns. Before writing or modifying code, load the relevant skill. Skipping skills leads to repeated bugs (e.g., blocking async, serde_json::Value on hot paths, missing VM tests).

Area Skill When to load
Overview /dev-capsem Orienting on any task, finding which skill to use
Quick start /dev-start First-time bootstrap, onboarding
Dev setup /dev-setup Environment setup, tool install, troubleshooting
Rust patterns /dev-rust-patterns Writing any Rust code in capsem-core/app/agent
MITM proxy /dev-mitm-proxy TLS, HTTP inspection, SSE parsing, ai_traffic
MCP /dev-mcp capsem-mcp server, MCP gateway, aggregator, builtin, tool routing
Use Capsem /use-capsem Agent-facing MCP VM tasks, input/output transfer, static file triage
Testing /dev-testing Running or writing tests, TDD, coverage
VM testing /dev-testing-vm In-VM diagnostics, capsem-doctor, session DB
Hypervisor testing /dev-testing-hypervisor Apple VZ / KVM, VirtioFS, vsock tests
Frontend testing /dev-testing-frontend vitest, svelte-check, visual verification
Python testing /dev-testing-python capsem-builder pytest, coverage, golden fixtures
Session DB /dev-session-debug Inspecting session.db, correlating events
Benchmarking /dev-benchmark capsem-bench, performance regression
capsem-doctor /dev-capsem-doctor In-VM diagnostic suite, adding new tests
Frontend /frontend-design UI components, Svelte 5 runes, Tailwind, owned semantic CSS
Build images /build-images capsem-builder, guest config, rootfs, kernel
Initrd repack /build-initrd Guest binary changes, fast iteration loop
Asset pipeline /asset-pipeline Asset manifest, hash verification, boot-time resolution
Just recipes /dev-just Which just command to run for a given task
Build/release gate /dev-gate Adding or changing a capsem-gate command; boundary, primitive, or contention guard failures
Citadel guards /citadel Adding a guard, a linter, or a source surface; a citadel test failing
Debugging /dev-debugging Bug investigation, reproduce-first workflow
CI triage /dev-ci Red gates, pr-gate failures, rerun decisions, stop-the-line policy
Cache control /dev-cache Cache inventory, retention, reuse, cleanup, or disk/Docker/Colima/Tart cache changes
Sprints /dev-sprint Running a multi-step feature sprint
Release /release-process CI, signing, notarization, changelog
Release gate proof /ironbank Black-box acceptance proof for VM, network, MCP, security, or release-gate behavior
Bug queue /dev-bug-review Working a queue of bug reports one-by-one (confirm, push back, fix, commit)
Installation /dev-installation Setup wizard, service registration, self-update, install tests
Architecture /site-architecture System design, service architecture, vsock, key files
Docs site /site-infra Writing/editing docs, Starlight, sidebar, release pages
Marketing site /site-marketing Marketing website (capsem.org), copy, components, theme
Skills system /dev-skills How skills work, naming, discovery
Skills layout /meta-organize-skills Skills directory conventions, symlinks
Skill discovery /meta-find-skills Finding or installing skills from the ecosystem
Skill authoring /meta-skill-creation Creating, improving, or evaluating skills

Desktop app (capsem-app)

  • Thin Tauri webview shell -- only IPC commands are log_frontend, open_url, dump_frontend_logs, and get_gateway_connection. No VM logic, no capsem-core dep. All UI state flows through the gateway. Desktop IPC pairs its strict loopback origin and bearer from private gateway.port/gateway.token files under the selected runtime directory (installed default port 19222).
  • The frontend is embedded in the Rust binary at cargo build time via tauri::generate_context!(). Running pnpm run build alone does nothing to a compiled binary. After any frontend change meant for the desktop app, run just build (frontend build + cargo build -p capsem-app). The toolbar shows build <timestamp> -- if it's stale, you forgot to rebuild the Rust binary.
  • Iframe src for bundled pages must be explicit (/vm/terminal/index.html). The Tauri custom protocol on macOS does not auto-append index.html the way dev servers do.

Code Style

  • Warnings are errors. Fix every compiler/linter warning before considering code done. Never leave warnings. Frontend: pnpm run check uses --fail-on-warnings. Rust: the root Cargo.toml sets [workspace.lints.rust] warnings = "deny" and every crate inherits it via [lints] workspace = true -- clippy and rustc warnings are build failures. New-stable clippy fallout is absorbed by documented allows in [workspace.lints.clippy], not per-file attributes.
  • Reuse over reinvention. Check capsem-core first. Extend existing abstractions.
  • Minimize code. Delete dead code, inline single-use helpers. Every line must earn its place.
  • Zero-copy first. Move or borrow existing buffers; do not clone, stringify, reparse or re-encode payloads for forwarding. Encode directly into one bounded final frame and derive its length there. Enforce limits during encoding and before receive allocation, without a separate sizing pass. This applies to terminal, graphical, gateway, guest and ledger paths. Preserve ownership and safety; document and measure any unavoidable payload copy.
  • Hashing and JSON require a documented reason. Internal data transport is typed binary MessagePack. Never expand binary bytes into JSON for admission, sizing, hashing or internal IPC. A hash must name the actual integrity, authentication, deduplication or compatibility contract it proves; a checksum supplied by the same potentially compromised owner is not independent proof. JSON needs an external protocol, documented persisted format or another concrete requirement. Record the reason beside the owning implementation and exercise that contract. Convenience, debugging and speculative tamper detection do not justify adding work to a hot path. See /dev-rust-patterns.
  • capsem-core is the shared library. Service, process, CLI, and agent crates are thin shells. Business logic lives in core.
  • One way to do things. Don't introduce a second pattern when one exists.
  • Rust tests live in a sibling tests.rs. In the parent module declare #[cfg(test)] mod tests; and put all #[test] functions in tests.rs next to it. Never append an inline mod tests { ... } block at the bottom of a production file -- it buries prod code under scroll-past test fixtures and doubles the file size for every Read and grep. See /dev-testing.

Fix what you find

Capsem's quality, security and performance are the responsibility of every coding agent working in it, not of whoever owns the area. They are P0, not something to trade for a smaller diff. See something, say something, do something.

When the work turns up a defect -- a bug, a lint or type violation, a failing guard, a stale path, a vulnerable dependency the audit flags -- fix it and commit it in the same session, in its own well-scoped commit. Do not stop to ask permission for a routine, low-risk, behavior-preserving fix, and do not route it to a follow-up ticket by default: a green gate is the bar, and the agent that found the problem is the one to fix it. This holds even when the defect predates your change or sits just outside the immediate task; the gate runs the whole tree, so the whole tree is your responsibility once you are in it.

The same holds for improvements you notice while coding, not just outright defects: a fragile path, a small refactor that makes the code clearer, a way to make it faster or more reliable. Do not walk past it as not your problem. If it is small, fix it test-first (a failing test, then the change -- see /dev-testing). If it is too big to fold into the current change, say so and open a GitHub bug (or ask to) with enough detail to act on, rather than dropping it silently.

A vulnerability is the strongest case of this rule, not an exception to it. When you find one -- a guest-to-host path, an auth or policy bypass, a leak, a missing no-follow -- fix it now, in this session, test-first, as its own security: commit, even when it sits outside the task you were given. Do not park it waiting for a decision, and do not describe an unfixed vulnerability in a public issue: the fix, its test and the CHANGELOG entry are the report. Tell the user what you found and what you changed while you do it.

Stop and ask only when the fix is genuinely a judgment call: it changes user-visible behavior, would weaken a security boundary (closing one is never a reason to wait), forces a real scope expansion, or picks between options a maintainer should choose (for example a dependency major-version bump, or suppressing an advisory instead of upgrading). The one advisory exception that needs no approval is RELEASE.md's time-boxed rule: no patched release exists anywhere, the package is build-time only in a dependency we do not control, and the entry expires within 30 days. State the finding and your recommendation, then act on the answer. A trivial patch bump, a lint fix, a stale-literal cleanup: just do it and say what you did.

Invariants (do not break)

Ephemeral VM model

Everything is ephemeral unless asked otherwise. VMs are temporary by default. Named VMs (capsem create -n <name>) are persistent -- workspace and rootfs overlay survive stops. capsem create is always detached; capsem run is the one-shot that destroys its VM on exit; capsem shell opens the TUI over existing sessions and creates nothing.

VirtioFS mode (default): fresh workspace + sparse rootfs.img per session. Persistent VMs store their session in ~/.capsem/run/persistent/.

Block mode (legacy): mke2fs unconditional at boot. Overlay upper is always tmpfs.

Guest binary security

All guest binaries deployed chmod 555 (read-only). Rootfs mounted read-only. Guest cannot modify its own binaries.

Codesigning

The binary must be codesigned with com.apple.security.virtualization or VZ calls crash. The justfile handles this.

Bound Direct Diagnostics

Any direct development command that can block, build, launch children, or wait on input must run through:

python3 build_system/scripts/ci/run-bounded-command.py --timeout-seconds <finite> -- <command>

The wrapper closes stdin and owns a process group so timeout or interruption cannot leave a Docker client, compiler, test runner, or helper behind. Do not use it around just test or either release command: the gate's config-owned timeouts, journal, resource teardown, and resumable graph remain authoritative.

Routine bounded commands do not acquire the global machine lock. Keep finite timeouts and process-tree cleanup, use Cargo's own build coordination, and continue writing code while focused checks run. Do not wait behind a broad gate for ordinary compilation or local TDD. The wrapper's --machine-lease option is reserved for an explicitly selected isolated measurement or qualification fixture; it is not the default development path. Complete qualification and release commands retain their own locking and exact-source isolation.

Serialized Orthogonal Releases

Release authority is RELEASE.md. Start there, then load /release-process for operational routing and hard-won implementation lessons. The public entrypoints are:

just release-binaries <channel> <source-commit>
just release-assets <channel> <source-commit>

Agents use these entrypoints rather than dispatching release workflows or authoring manifests directly.

A release is one branch, proven locally, then shipped. Every fix for a release goes on one branch (release/<version>) as its own commit, with the CHANGELOG updated in those commits. Run just test <branch-head> on this machine and keep fixing on the branch until it passes. Only then fast-forward main to that exact commit and run the release command on it. Never dispatch a release to find out what is broken: the hosted lane takes hours per attempt and stops at the first failure, so it finds one bug per attempt. The 0.6.4 stable release spent four dispatches, one per bug, that a local run catches in minutes. Nothing runs in CI that just test has not run locally first. The one escape is the release command's force argument, which the user asks for explicitly: it waives the local journal, never the citadel guards, release contracts, live advisory audit or hosted qualification.

A proof covers the tested commit and any commit with the identical Git tree. Fast-forward main to the tested commit (or merge only when the merge commit's tree equals it); anything else merged in needs its own proof, so do not merge anything else into main while its proof runs. Low-impact repeats are refused before expensive work and routed to focused owners; just test <source-commit> force "<reason>" is the audited exception and cannot be used twice consecutively. Direct release commands and just test own their timeouts, journal, teardown, and network boundary; do not wrap or nest them.

Implementation-specific invariants belong beside their executable tests and in the routed release references. If prose here disagrees with RELEASE.md, fix the routing instead of creating another release model.

Logger DB Boundary

Telemetry and security ledgers are database-owned.

  • Service routes, UI handlers, MCP helpers, and benchmark harnesses must not call rusqlite::Connection::open or DbReader::open directly.
  • They must not create service-owned logged-data projection caches.
  • Whether a ledger changed is the DB object's answer (read_cache_epoch, the reader's data_version sync), never the file's size or mtime: a WAL-only commit changes neither, and a route once served stale rows for months on that fingerprint. tests/citadel/test_db_freshness_boundary.py holds it.
  • They may own query intent, but the logger DB object owns query execution.
  • capsem-logger owns SQLite connection threads, mem/disk table layout, batching, flushing, WAL tuning, and future FTS5/search.
  • Do not hardcode route-specific query helpers in DbWriter as a substitute for this boundary. The DB object is an execution/storage owner, not a route semantics registry.
  • write(event).await means the event was accepted into the DB-owned producer buffer. Tests that need read-after-write visibility must use the DB flush barrier or shutdown/reopen; route code must not sleep, poll, or build a projection cache to make ledger rows appear.
  • Empty table means empty result. Missing table or column means broken schema and must fail loudly; do not add compatibility branches that treat missing ledger shape as empty data.

Every change touching logged data needs tests that guard this boundary.

Cryptographic Boundary

Every first-party Rust cryptographic operation goes through capsem-crypto, including build scripts, tests, and SDK code. Do not depend on or import raw hash, random, certificate, JWT, signature, constant-time or secret-erasure providers elsewhere. TLS configuration may use rustls transport types, while provider selection and key operations belong to capsem-crypto::tls.

Use BLAKE3 for new internal content fingerprints. Preserve SHA-256 wherever OCI, OAuth, release manifests or existing persisted identities require it; the owner supplies CPU-dispatched AWS-LC rather than a per-caller SHA backend. An algorithm identity change is a format migration, not a performance patch. Benchmark candidates on the actual CPU and I/O path before changing defaults. tests/citadel/test_crypto_boundary.py rejects direct providers and renamed dependencies, and the hot-build guard keeps hashing optimized in dev builds.

Host Unix Boundary

Every host syscall, file-descriptor operation and guest-writable filesystem access goes through capsem_foundation::unix. It is the one testable surface between Capsem and the kernel, so it is where no-follow, CLOEXEC, EINTR and errno handling are proven once instead of re-derived at each call site.

  • Application crates never call nix or libc. If foundation lacks a primitive, add it there, with tests, and call it.
  • Anything a guest can write -- the VirtioFS share, a workspace, a system image -- is reached through unix::contained::ContainedDir descriptors, never by path. A path is followed at open time, and a guest can swap any entry for a symlink between a check and a use.
  • Inside foundation, nix makes the syscall. A raw libc:: call is kept only where nix cannot express it, with the reason recorded.

tests/citadel/test_unix_boundary.py holds all three: an exact inventory of raw references outside foundation, an exact inventory of raw calls inside it, and adversarial cases for the spellings that used to slip through. Load /dev-rust-patterns before touching either.

The gate contract

The justfile dispatches; src/capsem/gate/ decides. No recipe carries a shell body and none exceeds five lines, both held by contract tests rather than convention.

Checked-in first-party scripts have the same architectural backstop. New scripts may not exceed [boundary.scripts].max_lines; larger historical files are an exact line-count debt ratchet, not an exemption list. Split growth before merging, and lower or remove a ratchet whenever a script shrinks. The guard only inventories Git-tracked program sources under the configured first-party roots, so generated outputs and vendored dependencies are outside its scope by rule.

just test is one process, one machine lock, one workspace, one plan. Its dry run reports the current totals; conditional asset staging makes a checked-in count depend on machine state. A passing run for the exact commit is required before that commit is released.

Six rules, each with a guard:

  • A plan action may never invoke just or another capsem-gate command. GuardedRunner refuses it at runtime. The machine lock is not reentrant, so every such call was a child waiting out its 7200-second timeout for the lock its own parent held. Compose the other command's fragment instead.
  • Work is composed from primitives. actions and fileactions are the only modules that touch the machine, alongside the four that own one piece of machine state as their whole purpose. Anything else going around them is work the dry run cannot show and the run log cannot time.
  • Ordering is declared, then derived. A step names what it must follow; graphlib decides the sequence. A cycle fails before any step runs. Never sequence by writing one plan.add above another.
  • Contention is declared in [execution.exclusives], with the reason. Two steps can be independent and still unable to share the machine.
  • Teardown is held(...) -- acquired in order, released in reverse, evidence preserved before release. A finally that removes a directory is a Resource that was not written.
  • Every value lives in config/gate.toml. No path, filename, architecture or channel name in code.

Two more, which follow from the first:

  • A plan describes; it does not act. plan() is built with the machine sealed, so --dry-run cannot touch it -- and inspection is answered before any re-exec, or asking becomes doing.
  • Anything that writes takes the machine lock. [execution.exclusives] entries are threading.Locks: they order steps inside one plan and coordinate nothing between two capsem-gate processes.

One gate runs per machine, enforced by flock rather than a pidfile, and every run is recorded under cache/target/gate-runs/ and bounded by the stages.gate-runs cache contract. The run log is written by the runner rather than by call sites, so nothing can be forgotten into invisibility.

Read /dev-gate before changing any of it.

Vocabulary and gotchas

  • glowup = installed-package release proof owned by just test: Linux runs build_system/scripts/release/local-release-glowup.py in Docker/systemd; macOS installs the signed exact package in Tart and boots it through physical Apple VZ.
  • winterfell = service session-ledger lifecycle fixtures in crates/capsem-service/src/tests.rs; AGENTS.md's gate list refers to these.
  • just test writes benchmark recordings under cache/target/tests/benchmarks/; intentional historical publication uses the owning benchmark command and explicit review.
  • Rust is pinned to 1.97.1 in rust-toolchain.toml, bootstrap, CI, and Docker. Bump every surface together in a deliberate monthly toolchain PR and handle new-lint fallout there.
  • Bare python3 on macOS is 3.9 and gives wrong answers (phantom syntax errors, silently dead hooks). Run repository Python with uv run --project build_system --frozen python.
  • Never cd into a subdirectory in an agent shell. The working directory persists across calls, and a worktree session cannot move it back: one cd crates/.../src once left every later shell call, subagents included, failing its PreToolUse hook. Use absolute paths, git -C, or (cd dir && ...) in a subshell. Agent hooks in .claude/settings.json run under uv for the same reason, and resolve their scripts from git rev-parse --show-toplevel -- the checkout the session works in -- not $CLAUDE_PROJECT_DIR alone: in a worktree session that names the main checkout, and when it sat on an older commit without the hook script every Bash call failed until restart.
  • A same-size mutation of a Python test file can be served from a stale .pyc. Clear tests/__pycache__ before trusting a mutation-and-restore check.
  • Processes under any Seatbelt profile cannot exec setuid binaries: /bin/ps fails. Use syscalls, and prove sandbox fixes under sandbox-exec.
  • A failing kingslanding test reproduces in about two minutes by running its one pytest file in the latest cache/worktrees/<run>; re-sign rebuilt binaries ad hoc with the virtualization entitlement.
  • Red CI is usually per-job provisioning, not code. Check the environment before changing source.
  • tokio-unix-ipc closes a descriptor before deregistering it, which lost IPC replies under churn (#209). Write the churn stress test before believing an IPC hypothesis.

Working rules

How to spend effort and the shared machine. Each one is a mistake that already happened.

  • Write code, verify locally, qualify by phase. During implementation use TDD and only the focused tests, formatting, type or lint checks necessary for the changed files and behavior. Do not run whole crates, workspace/Citadel suites, VM/install lanes or benchmarks as a routine per-commit ritual. At each major phase boundary qualify its complete outcome; before merge or release run the authoritative final proof on the exact source. Iterate on failures with the smallest reproducer. Record incomplete proof honestly and keep independent implementation moving.
  • Use the most focused lens that proves the change. A handful of VM tests run directly with pytest in the latest cache/worktrees/<run>, against the assets that run already built (minutes). A subsystem uses its focus-test <owner> (kingslanding for containers, assets for a guest rebuild). focus-test functional --slow runs once, on the final head; a two-hour lane between five-line changes is how a sprint stops moving. If no owner fits, ask the user before adding one.
  • A narrow suite is not verification. tests/test_gate_*.py alone misses what the contract suites catch; the final run is the whole suite.
  • A guard must not fake what it guards. Stubbing the input under test makes the test green by construction.
  • Measure before and after, twice. Establish the noise floor by running the unchanged code twice before blaming a difference on a change. Performance work reports both runs side by side.
  • Polled API reads stay in milliseconds. Info, stats and status routes are what the TUI, tray and web poll. A memory or disk fix never buys itself a slower poll: record the reader benches before and after, keep an idle poll well under 1 ms, and index a slow query instead of caching it in RAM.
  • Check real exit codes. Use pipefail and $?; never judge a run through grep-filtered output.
  • Shared builds, local checks. Every checkout builds into the main checkout's cache/target/cargo. Run Cargo through the bounded wrapper for timeout and cleanup, without a routine machine lease. Do not set a private CARGO_TARGET_DIR; retain cross-checkout reuse. Do not diagnose build contention by restarting sccache or serializing agents through chat.
  • Never kill by pattern. pkill -f with options parsed as extra patterns once killed every user process. Process control is a tested Python module acting on exact pids.
  • Work in your assigned worktree. Editing the main checkout once killed a running release at source.verify. One agent commits in a worktree at a time.
  • Never git stash to inspect. Read old content with git show <rev>:<path>; a stash pulls uncommitted work out from under running builds.
  • One crate per commit, paths staged explicitly.
  • Follow-ups are GitHub issues with file references and a fix sketch, never notes left in an agent's scratch space.

Commits

  1. Update CHANGELOG.md in the same commit when the change is user-visible. Refactors, test-only changes and internal cleanups do not need an entry. This used to read "every commit", which 39 of the last 100 did not do -- a rule nobody follows teaches that the neighbouring rules are advisory too, and the neighbours here are the DB boundary and the release contract.
  2. Stage files explicitly (no git add -A)
  3. Conventional subject, type(scope): summary. In use: feat, fix, refactor, perf, test, docs, chore, style, security, merge.
  4. Author: Elie Bursztein github@elie.net
  5. No Co-Authored-By trailers

Logging

Boot sequence instrumented with tracing spans. RUST_LOG=capsem=debug for full timing, RUST_LOG=capsem=info for top-level.