diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index f9cb045..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,72 +0,0 @@ -# Changelog - -History before 0.5.0 lives in git and in [drill/RUNS.md](drill/RUNS.md), -which records not just what changed but what each drill run proved. - -## 0.5.0 — 2026-07-15 - -The release the project was renamed in: the repo is `heavy-duty/box`, matching -the CLI it ships. Everything legacy-facing is honored forever — the -`user.claudebox=1` tag, the `.claudebox/` runbook folder, the old symlink the -installer retires — but nothing current carries the old name. - -### Added - -- **`codex` and `grok` templates** — OpenAI Codex CLI and xAI Grok CLI boxes, - creds-free like every template. The template mechanic (image + user + - resources, never a network or a `security.*` key) now has three tenants - beside `blank`, and the drill mints all of them cold. -- **`box expose []`** — a deliberate, loopback-only - door to a port inside a box, for seeing a dev server in your browser. The - listen side is always the host's `127.0.0.1` (no flag to widen it), the door - is per-port, `--list`/`--remove` manage it, and `box info` shows open - exposures — a box with a hole says so. -- **Inline resource overrides on `new`** — `--cpu --memory - --disk ` (#57). Resolution most-specific-first: flag > `BOX_CPU` / - `BOX_MEMORY` / `BOX_DISK` environment (the scripting form) > template - `box.env` > defaults. Values pass to Incus verbatim; resources are all a - flag can touch. `--from` refuses them — a clone carries its source's - resources. -- **Host lifecycle as verbs** — `box setup-host`, `box teardown-host`, and - `box migrate-host`, which re-homes pre-0.4.0 boxes onto the current stack - (`--box ` / `--all-boxes`, authed state preserved) and retires the legacy - bridge once empty (`--retire-legacy`). -- **The `.box/` recipe convention** — the agent-facing runbook folder a repo - can ship, renamed from `.claudebox/` (both spellings read). - -### Fixed - -- **VM mints no longer hang at GRUB** — Incus defaults VMs to Secure Boot on, - and a cloud image whose shim the host's OVMF doesn't trust dies with "bad - shim signature" forever. Boxes now launch with `security.secureboot=false`; - the VM boundary, not boot attestation, is the box threat model. -- **`box expose` actually delivers packets** — a trilogy of drill-found - absences: the NAT proxy needs the box's boxnet lease pinned as a static - `ipv4.address` (Incus resolves `connect=0.0.0.0` against device config, not - the lease); a loopback-sourced packet needs `route_localnet` plus a - masquerade on the bridge to leave the host and be answerable; and the box's - replies need a `ct state established,related` accept ahead of the host - firewall's input drop, which was eating them statelessly. Boxes still - cannot initiate toward the host — a box-originated SYN is a NEW flow. -- **Firewall rules now converge on upgrade** — `box-firewall.sh` rebuilds its - chains every run (add + flush + re-add) instead of skipping when they - exist, which had pinned every host to the rule set of the release that - first ran there. -- **Failed mints tell you why** — cloud-init failures print the box's own log - excerpts and leave the box up to inspect; a mint that never boots names the - likely cause (corrupt image, Secure Boot, GRUB hang) and ships a sanitized - console dump; the installer asserts it landed the ref it was asked for. -- **`grok` installs the binary it actually ships** — the installer was read, - not guessed at, and the CLI lands on the non-interactive PATH (same fix - class as codex). - -### Changed - -- **Debrand complete** — env vars, install dir, docs, template descriptions - and the README all say `box`; the install URL is - `heavy-duty/box` (GitHub redirects the old one, `BOX_REPO` overrides). -- **The drill grew from 47 to 83 checks** — the expose door opened, exercised - and shut (with the contract re-probed around it), every template minted - cold, a faithful pre-0.4.0 box re-homed through `migrate-host`, and the - inline resource flags asserted (including their precedence over the - environment). diff --git a/README.md b/README.md index b84dd24..b0d5cd6 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ # box **Headless, trust-less, throwaway dev VMs.** One command mints a fresh, -network-isolated Incus box from a **template**; the coding-agent templates -ship a CLI agent on Debian 13 — `claude` (Claude Code), `codex` (OpenAI -Codex), `grok` (xAI Grok). The box is the product — you log in and work; -destroying it loses nothing you didn't push. +network-isolated Incus box from a **template**; the flagship template is +`claude` — Debian 13 with Claude Code installed. The box is the product — +you log in and work; destroying it loses nothing you didn't push. **Strictly creds-free.** A box ships with everything installed and **no** -credentials — no agent token, no git PAT, nothing. You authenticate +credentials — no Claude token, no git PAT, nothing. You authenticate interactively *inside* the box. The tool never stores or injects a secret. That means there's nothing shared or committed, so it's safe for multiple operators out of the box. @@ -19,8 +18,8 @@ nobody home — not a box with the safety off. **The tool knows nothing about your projects.** You just `git clone` inside a box. A repo can ship an optional [`.box/`](docs/box-recipe.md) -runbook that the box's coding agent reads and acts on — there is no `install` -step and no host-run setup. See [docs/box-design.md](docs/box-design.md) for the +runbook that Claude Code reads and acts on — there is no `install` step and no +host-run setup. See [docs/box-design.md](docs/box-design.md) for the design rationale. > **0.5.0**: two new templates (`codex`, `grok`), `box expose` — a @@ -67,26 +66,23 @@ legacy box remains. ## Quick start ```sh -box new --name work --template claude # a creds-free coding-agent box (~10 min cold) +box new --name work --template claude # a creds-free Claude box (~10 min cold) box shell work # enter as the template's user ``` -Pick whichever coding-agent template you like — `claude`, `codex`, `grok` — or -`blank` for none. Inside the box, authenticate as needed. The `claude` template -looks like this; the others follow the same shape with their own login step: +Inside the box, authenticate as needed: ```sh claude # then run /login — copy the URL (press c), open it # in YOUR browser, paste the code back. No host CLI needed. gh auth login # or drop a PAT in — your git credentials, your call git clone https://github.com/you/project && cd project -claude # if the repo has .box/, the agent reads it and sets up +claude # if the repo has .box/, Claude reads it and sets up ``` ## Templates -No coding agent is special — each is one template among several, and adding -another is just another directory. What ships today: +The claude box is one template among several. What ships today: | Template | What's in it | | --- | --- | @@ -110,10 +106,9 @@ box new --name scratch # the DEFAULT template is blank: bare Debian, A template **cannot** name a network, a profile, or a `security.*` flag — there is no key for them. Every box launches with the shared `box-net` profile (the isolated NIC + root disk), so every template gets the identical -trust boundary. Resources come from the template's `box.env`, overridable at -mint time — inline (`--cpu 2 --memory 3GiB --disk 20GiB`) or via -`BOX_CPU` / `BOX_MEMORY` / `BOX_DISK` environment variables (the scripting -form; flags win). The template's identity (name, user) is stamped onto the instance, +trust boundary. Resources come from the template's `box.env`; +`BOX_CPU` / `BOX_MEMORY` / `BOX_DISK` environment variables override them at +mint time. The template's identity (name, user) is stamped onto the instance, so `shell`, `exec` and `tmux` land in the right user — and a clone still knows, because `incus copy` carries the metadata. @@ -127,7 +122,7 @@ box snapshot work authed # checkpoint after you've logged in box new --name feature --from work/authed # clone the authed state into a new box ``` -`--from` copies the whole box (agent login, git creds, clones and all) while +`--from` copies the whole box (Claude login, git creds, clones and all) while preserving isolation. You can also `box new --name x --from work` to clone a box's live state, or roll a box back with `box restore work authed`. @@ -157,7 +152,7 @@ the door is per-port, punched and removable at runtime. ## Commands ``` -box new --name [--template ] [--from [/]] [--cpu ] [--memory ] [--disk ] [--vm|--container] +box new --name [--template ] [--from [/]] [--vm|--container] box templates # list the templates this install can mint box list # list your boxes box info # one box: state, IP, exposures, snapshot labels @@ -238,9 +233,8 @@ enforces it, layer by layer: that door only ever opens onto the host's own loopback (`127.0.0.1`), never the network. -The VM is the trust boundary: whatever runs inside — the coding agent, or -anything a template ships — can run arbitrary code and touch nothing you care -about. +The VM is the trust boundary: whatever runs inside — Claude, or anything a +template ships — can run arbitrary code and touch nothing you care about. ### Measured, not claimed @@ -252,16 +246,36 @@ drill installs the whole stack, mints every template cold, snapshots and clones, probes every boundary from inside the boxes, opens and shuts the `expose` door (and checks the contract survives it), re-homes a faithful pre-0.4.0 box through `migrate-host`, and removes what it minted — -currently **83 checks**. [drill/RUNS.md](drill/RUNS.md) is the full +currently **81 checks, 81 passing**. [drill/RUNS.md](drill/RUNS.md) is the full history, including every trap that fooled a run into a wrong verdict. +### Run the drill yourself + +The drill ships in the repo, not the installed tree — run it from a checkout. +Two versions are in play and both must be current: **the drill script you +run** (a stale checkout judges the past), and **the code under test** — the +drill does not test your working tree; it installs box from GitHub +(default: `heavy-duty/box@main`) and asserts the installed tree is exactly +the ref it asked for before issuing any verdict. + ```sh +git clone https://github.com/heavy-duty/box && cd box # or refresh an existing +git log --oneline -1 # checkout — this commit is + # the drill that will judge bash drill/doctor.sh # read-only: is this host healthy and the stack live? bash drill/drill.sh # FULL end-to-end — mutates the host; use a machine you own bash drill/wipe.sh # scorched earth: strip BOTH name generations, images and # (--purge-storage) the pool, so a run starts from bare ``` +To drill something other than latest `main` — a release ref, or a PR branch +on a fork: + +```sh +bash drill/drill.sh --ref +bash drill/drill.sh --repo / --ref # a PR under review +``` + The doctor reads ground truth, not config claims — the kernel's `isolated on` flag per bridge port, the process table, the resolver actually in use — and diagnoses the host faults that have actually happened: a wedged Incus daemon, @@ -271,10 +285,9 @@ inherit. ## Recipes: the `.box/` convention A repo that wants to be easy to stand up in a box ships an optional `.box/` -folder — a runbook the box's coding agent reads and follows (install deps, -start services, template env, seed data, smoke-test). It is agent-facing -documentation, not a host-executed script. See -[docs/box-recipe.md](docs/box-recipe.md). +folder — a runbook Claude reads and follows (install deps, start services, +template env, seed data, smoke-test). It is agent-facing documentation, not a +host-executed script. See [docs/box-recipe.md](docs/box-recipe.md). ## Uninstall @@ -287,6 +300,6 @@ rm -rf ~/.local/share/box ~/.local/bin/box # the CLI itself ## Non-goals - **No unattended/CI bring-up.** The flow is interactive (log in, clone, ask - the agent). Reproducible-by-construction provisioning is out of scope. + Claude). Reproducible-by-construction provisioning is out of scope. - **No credential storage or injection by the tool.** Boxes are creds-free; snapshots are the reuse mechanism, not a secrets store. diff --git a/bin/box b/bin/box index 63be4de..b55fd68 100755 --- a/bin/box +++ b/bin/box @@ -7,7 +7,6 @@ set -euo pipefail root="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/.." && pwd)" remote=""; mode="auto"; name=""; from=""; template=""; force=0; json=0; want_help=0 -cpu=""; memory=""; disk="" inst="" # the resolved Incus instance, set by the 'box' precondition die() { echo "box: $*" >&2; exit 1; } # 1 = it went wrong @@ -38,7 +37,7 @@ version() { echo "box $(cat "$root/VERSION" 2>/dev/null || echo unknown) ($root) # doesn't enforce a box invariant, it is incus's job, not ours — that is # what `box incus` is for. CMDS=( - "new^--name [--template ] [--from [/]] [--cpu ] [--memory ] [--disk ] [--vm|--container]^^Mint a box from a template (default: blank), or --from an existing box/snapshot^fn:cmd_new^" + "new^--name [--template ] [--from [/]] [--vm|--container]^^Mint a box from a template (default: blank), or --from an existing box/snapshot^fn:cmd_new^" "templates^^^List the templates this install can mint^fn:cmd_templates^" "list^[--json]^^List your boxes^fn:cmd_list^" "info^ [--json]^box^One box: state, type, IP, and its snapshot labels^fn:cmd_info^" @@ -203,24 +202,17 @@ being told. A template sets image, user and resources — never the network: every template gets the same isolation. --from [/] Clone src's live state, or its snapshot . - --cpu CPUs for this mint (limits.cpu, verbatim to Incus). - --memory RAM for this mint, e.g. 3GiB (limits.memory). - --disk Root disk size, e.g. 20GiB. VM mode only — a - container's root rides the storage pool. --vm | --container Force the mode. VM is the trust boundary and the default wherever /dev/kvm exists; container mode (security.nesting=true) is the fallback for hosts without nested virt — weaker isolation, dev/test only. -Resources resolve most-specific-first: these flags, then BOX_CPU / -BOX_MEMORY / BOX_DISK environment variables (the scripting form), then the -template's box.env, then defaults. Flags shape a fresh mint only — a --from -clone carries its source's resources. Resources are all a flag can touch: -there is no flag for a network or a security key, on purpose. +Resources come from the template's box.env; BOX_CPU / BOX_MEMORY / BOX_DISK +environment variables override them at mint time (a small host shrinks a box +without editing a template it doesn't own). box new --name scratch # blank, the default box new --name work --template claude - box new --name lean --template claude --cpu 2 --memory 3GiB box new --name feature --from work/authed EOF ;; @@ -451,9 +443,6 @@ while [ $# -gt 0 ]; do --name) [ $# -ge 2 ] || usage_error "--name needs a value"; name="$2"; shift 2 ;; --from) [ $# -ge 2 ] || usage_error "--from needs a value"; from="$2"; shift 2 ;; --template) [ $# -ge 2 ] || usage_error "--template needs a value"; template="$2"; shift 2 ;; - --cpu) [ $# -ge 2 ] || usage_error "--cpu needs a value"; cpu="$2"; shift 2 ;; - --memory) [ $# -ge 2 ] || usage_error "--memory needs a value"; memory="$2"; shift 2 ;; - --disk) [ $# -ge 2 ] || usage_error "--disk needs a value"; disk="$2"; shift 2 ;; --remote) [ $# -ge 2 ] || usage_error "--remote needs a value"; remote="$2:"; shift 2 ;; --vm) mode=vm; shift ;; --container) mode=container; shift ;; @@ -651,14 +640,11 @@ load_template() { esac done <"$dir/box.env" [ -n "$T_IMAGE" ] && [ -n "$T_USER" ] || die "template '$t': BOX_IMAGE and BOX_USER are required" - # Resolution, most specific wins: inline flag (--cpu/--memory/--disk, #57) - # > BOX_* environment (how a small host or the drill shrinks every box it - # mints) > the template's file > defaults. Values pass to Incus verbatim — - # its units, its validation; box adds no parser of its own. Resources only: - # there is still no flag for a network or a security.* key, on purpose. - T_CPU="${cpu:-${BOX_CPU:-${T_CPU:-4}}}" - T_MEMORY="${memory:-${BOX_MEMORY:-${T_MEMORY:-8GiB}}}" - T_DISK="${disk:-${BOX_DISK:-${T_DISK:-60GiB}}}" + # Environment overrides beat the file — this is how a small host (or the + # drill) shrinks a box without editing a template it doesn't own. + T_CPU="${BOX_CPU:-${T_CPU:-4}}" + T_MEMORY="${BOX_MEMORY:-${T_MEMORY:-8GiB}}" + T_DISK="${BOX_DISK:-${T_DISK:-60GiB}}" } cmd_templates() { @@ -678,7 +664,6 @@ cmd_new() { local instance; instance="$(iname_of "$name")" if [ -n "$from" ]; then [ -z "$template" ] || usage_error "--from clones an existing box; its template rides along (drop --template)" - [ -z "$cpu$memory$disk" ] || usage_error "--cpu/--memory/--disk shape a fresh mint; a clone carries its source's resources ('box incus' can change them afterwards)" local src="${from%%/*}" snap="" srcref case "$from" in */*) snap="${from#*/}" ;; esac srcref="$(iname_of "$src")"; [ -n "$snap" ] && srcref="$srcref/$snap" @@ -700,9 +685,6 @@ cmd_new() { # turning it off boots reliably across image rebuilds. Container mode has # no firmware, so it does not apply there. if [ "$m" = vm ]; then extra+=(--vm --device "root,size=$T_DISK" --config security.secureboot=false); else extra+=(--config security.nesting=true); fi - # Root size is a VM launch concern; a container's root rides the pool. Say - # so instead of silently dropping an explicit --disk. - [ "$m" = vm ] || [ -z "$disk" ] || echo "box: note — --disk applies to VM mode only; this container's root rides the pool" >&2 # The template's identity is stamped ONTO the instance: which template, # which user. 'incus copy' preserves user.* keys (audit B2), so a clone # knows what it is without ever consulting the template again. diff --git a/docs/box-design.md b/docs/box-design.md index 834165a..efc41ad 100644 --- a/docs/box-design.md +++ b/docs/box-design.md @@ -1,30 +1,28 @@ # box design `box` is a CLI that mints and manages **trust-less, network-isolated VMs -with a coding agent installed** (`claude`, `codex`, `grok`, or `blank` for -none). It is infrastructure, not a project provisioner. +with Claude Code installed**. It is infrastructure, not a project provisioner. See issue #3 for the full reframe and rationale. This doc captures the durable design decisions. ## Principle: separate the tool from the agent -- **The tool** mints isolated boxes with the agent installed but **unauthenticated**. +- **The tool** mints isolated boxes with Claude installed but **unauthenticated**. It knows nothing about projects, secrets, recipes, or memory. -- **The agent** (Claude Code, Codex, Grok — whichever template, inside the box) - reads an optional `.box/` runbook in a cloned repo and acts on it. The recipe's - consumer is the reasoning agent, not host machinery. +- **The agent** (Claude Code, inside the box) reads an optional `.box/` + runbook in a cloned repo and acts on it. The recipe's consumer is the + reasoning agent, not host machinery. ## Boxes are strictly creds-free `box new --name ` launches a blank box: everything installed, **no** -git credentials and **no** agent credentials. The operator authenticates +git credentials and **no** Claude credentials. The operator authenticates interactively *inside* the box: -- **The coding agent** — e.g. `claude` → `/login` (paste-a-code OAuth: copy the - URL, open it in your own browser, paste the code back); `codex` and `grok` - have their own login step. Works because the box is outbound-only; the tool - never handles a token. +- **Claude** — `claude` → `/login` (paste-a-code OAuth: copy the URL, open it in + your own browser, paste the code back). Works because the box is outbound-only; + the tool never handles a token. - **Git** — the operator adds their own PAT / `gh auth login` inside the box. The tool stores and injects **no** credentials, ever. This dissolves the @@ -38,16 +36,15 @@ snapshots, not a secrets store: - `box snapshot [label]` — checkpoint after login + clone. - `box new --name --from [/]` — clone an existing box or snapshot (authed state and all). Isolation is preserved: the clone keeps - the `box-net` profile + `boxnet` + ACL. + the `claude-dev` profile + `claudenet` + ACL. - `box restore ` — roll a box back to a checkpoint. Log in once → snapshot → spin up authed boxes from it. ## The box announces itself to the agent -cloud-init installs a global agent-context file in every coding-agent box -(`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, `~/.grok/AGENTS.md`) telling the -agent it is running in a box (trust-less, ephemeral, creds-free) and to treat a +cloud-init installs a global `~/.claude/CLAUDE.md` in every box telling Claude it +is running in a box (trust-less, ephemeral, creds-free) and to treat a repo's `.box/` folder as its bootstrap runbook. No "tell it" step, no host execution. @@ -65,7 +62,7 @@ forever — and wrapping them one at a time grows a worse `incus`. The rule: > **box owns a command when it must enforce an invariant Incus cannot see:** > the `user.box=1` boundary (never touch an instance we didn't mint), the -> isolation stack (`box-net` profile + `boxnet` + ACL), or the creds-free +> isolation stack (`claude-dev` profile + `claudenet` + ACL), or the creds-free > snapshot→clone workflow. Everything else is Incus's job. The rule cuts both ways, and that's the point: @@ -95,7 +92,7 @@ warns and proceeds — from there the trust boundary is yours to keep. ## Isolation -Dedicated NAT bridge `boxnet` + Incus `box-isolate` ACL dropping all +Dedicated NAT bridge `claudenet` + Incus `claude-isolate` ACL dropping all RFC1918/CGNAT/link-local egress, plus host-firewall rules blocking instance → host. Entry is `incus exec` over the local socket — no inbound path. The VM is the trust boundary. @@ -104,12 +101,12 @@ the trust boundary. That last clause is the one that was assumed and turned out to be false, so it is spelled out here with the mechanism, and `drill/` tests it on every run. -- **Box → host, LAN, RFC1918, CGNAT, link-local:** the `box-isolate` ACL. +- **Box → host, LAN, RFC1918, CGNAT, link-local:** the `claude-isolate` ACL. - **Box → box: an nftables *bridge-family* rule** (`host/box-firewall.sh`). It cannot be an ACL rule. Two boxes on one bridge share an L2 segment, so their frames are *switched* between bridge ports and never traverse the netfilter path an L3 ACL lives on — the ACL looked airtight (it drops - `10.0.0.0/8`, which contains `boxnet`) while box→box was in fact wide open. + `10.0.0.0/8`, which contains `claudenet`) while box→box was in fact wide open. A live probe found box A's SYN arriving at box B. The bridge family's forward hook fires exactly on port-to-port frames, which on this bridge means box→box and nothing else: gateway traffic and routed egress are delivered locally, not diff --git a/docs/box-recipe.md b/docs/box-recipe.md index 6544c93..6d4f345 100644 --- a/docs/box-recipe.md +++ b/docs/box-recipe.md @@ -1,15 +1,14 @@ # The `.box/` convention -`box` mints trust-less, creds-free, isolated VMs with a coding agent already -installed (`box new/shell/snapshot/restore/exec/down/start/rm/status`) — the -`claude`, `codex`, and `grok` templates each ship a CLI agent. The tool knows -**nothing** about your project. There is no `install` step and no host-run -setup script. +`box` mints trust-less, creds-free, isolated VMs with Claude Code already +installed (`box new/shell/snapshot/restore/exec/down/start/rm/status`). The +tool knows **nothing** about your project. There is no `install` step and no +host-run setup script. A project makes itself easy to stand up inside a box by shipping an optional `.box/` folder. This folder is **agent-facing documentation** — read and -acted on by the box's coding agent (the reasoning agent) running inside the -box, whichever template you minted. It is not shell that the host executes. +acted on by Claude Code (the reasoning agent) running inside the box. It is not +shell that the host executes. > The folder was named `.claudebox/` before the 0.5.0 rename. Repos that still > ship `.claudebox/` keep working — the agent is told to read either — but new @@ -27,17 +26,15 @@ box, whichever template you minted. It is not shell that the host executes. ## How it's consumed -Every coding-agent box ships a global agent-context file — `~/.claude/CLAUDE.md` -for `claude`, `~/.codex/AGENTS.md` for `codex`, `~/.grok/AGENTS.md` for `grok` — -telling the agent it is inside a box and to treat a repo's `.box/` folder as its -bootstrap runbook. So the whole flow is (shown with `claude`; the other agents -follow the same shape): +Every box ships a global `~/.claude/CLAUDE.md` telling Claude it is inside a +box and to treat a repo's `.box/` folder as its bootstrap runbook. +So the whole flow is: ``` box new # get a box box shell # get in git clone && cd -claude # the agent reads .box/ and brings the project up +claude # Claude reads .box/ and brings the project up ``` The operator can also just say: *"set this project up per .box"*. @@ -48,9 +45,9 @@ Author everything here for a reasoning agent. - **`.box/SETUP.md`** — the prose runbook. Prerequisites, how to install deps, how to start services, how to template the env, how to seed data, and - how to smoke-test. Written as instructions to the agent. -- **Helper scripts** (e.g. `.box/dev-up.sh`) that the runbook tells the agent - to run. The agent decides to run them; the host never does. + how to smoke-test. Written as instructions to Claude. +- **Helper scripts** (e.g. `.box/dev-up.sh`) that the runbook tells Claude + to run. Claude decides to run them; the host never does. - **`.box/env.template`** — example env the runbook explains how to fill. Staging values the operator pastes in. **Never commit real secrets.** - **`.box/compose.yml`** — optional services the runbook starts. @@ -73,7 +70,7 @@ This is a Node service backed by Postgres. 6. Smoke-test: `curl -sf localhost:3000/health` should return `{"ok":true}`. ``` -That's it — the agent reads it top to bottom and adapts if reality differs. +That's it — Claude reads it top to bottom and adapts if reality differs. ## Guidance @@ -82,4 +79,4 @@ That's it — the agent reads it top to bottom and adapts if reality differs. - **Never put real credentials in `.box/`.** Templates and staging placeholders only. The operator pastes real values at runtime. - **No `.box/` is fine.** The operator can stand the project up by hand, - or let the agent infer the steps from the repo's `README` / `CLAUDE.md`. + or let Claude infer the steps from the repo's `README` / `CLAUDE.md`. diff --git a/drill/README.md b/drill/README.md index d7a06cf..cdb41e9 100644 --- a/drill/README.md +++ b/drill/README.md @@ -84,9 +84,9 @@ a **design veto** for #16, caught before the code is written. ## What it does not check -agent login (e.g. `claude /login`) — it's interactive by design, and the box is -creds-free by design. The drill confirms each coding-agent template's CLI is -installed and runnable; authenticating is yours. +`claude /login` — it's interactive by design, and the box is creds-free by +design. The drill confirms Claude Code is installed and runnable; authenticating +is yours. If the host has no `/dev/kvm`, box falls back to container mode. The drill still runs, but it says loudly that **the VM trust boundary was not validated** diff --git a/drill/drill.sh b/drill/drill.sh index 9d60bae..8e56575 100755 --- a/drill/drill.sh +++ b/drill/drill.sh @@ -32,7 +32,7 @@ # false FAILs on the first live run. The pipeline verdict must be grep's alone. set -u -REPO="${BOX_REPO:-heavy-duty/claudebox}" +REPO="${BOX_REPO:-heavy-duty/box}" REF="${BOX_REF:-main}" YES=0; KEEP=0 SELF="$(readlink -f "$0")" @@ -422,24 +422,13 @@ box new --name tpl --template cbdrill-bad 2>&1 | grep -q "unknown key 'BOX_NETWO || no "a box.env key outside the allowlist was ACCEPTED — a template could weaken isolation" rm -rf "$badt" -# Inline resource flags (#57): refused on a clone, honored on a mint. The -# mint proof rides the blank box below — and because this drill exports -# BOX_CPU/BOX_MEMORY on small hosts, it is also the precedence proof -# (flag > env > template > default). -box new --name tpl --from nowhere --cpu 2 2>&1 | grep -q 'carries its source' \ - && ok "resource flags refused on --from — a clone carries its source's resources" \ - || no "--from accepted a resource flag (should refuse: clone resources come from the source)" - printf '\n minting a blank box (the DEFAULT template — no tooling, fast)…\n' t0=$SECONDS -if mint_box /tmp/mint-tpl.log --name tpl --cpu 1 --memory 1GiB; then +if mint_box /tmp/mint-tpl.log --name tpl; then ok "box new --name tpl, no --template ($((SECONDS - t0))s)" tt="$(incus config get tpl user.box.template 2>/dev/null)" [ "$tt" = blank ] && ok "the default template is blank (user.box.template=blank)" \ || no "default template is '${tt:-}' — expected blank" - rc="$(incus config get tpl limits.cpu 2>/dev/null)/$(incus config get tpl limits.memory 2>/dev/null)" - [ "$rc" = "1/1GiB" ] && ok "inline --cpu/--memory landed (limits = $rc, beating BOX_* env)" \ - || no "inline resource flags did not land — limits are $rc, expected 1/1GiB" [ "$(incus config get tpl user.box.user 2>/dev/null)" = dev ] \ && ok "template user stamped on the instance (user.box.user=dev)" || no "user.box.user not stamped" incus config show tpl 2>/dev/null | grep -q '^- box-net' \