Merge pull request #109 from dan-claude-bot/feat/kimi-box-tenant

feat: kimi-box joins the box tenant roles — the Kimi CLI agent guest
This commit is contained in:
Daniel Marin 2026-07-22 16:06:45 +01:00 committed by GitHub
commit 595a152925
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 62 additions and 23 deletions

View file

@ -6,6 +6,10 @@ on the way to cutting its first release, and this file starts there.
## Unreleased ## Unreleased
### Added
- `kimi-box` joins the box tenant roles — the Kimi CLI agent guest (#109)
## 0.3.0 — 2026-07-21 ## 0.3.0 — 2026-07-21
### Fixed ### Fixed

View file

@ -4,7 +4,7 @@ A CLI that turns a **pristine Debian server into a hardened, tailnet-joined
node** — one curl, one command. A second command installs a version-pinned node** — one curl, one command. A second command installs a version-pinned
Coolify on a control-plane box. And inside a [box](https://github.com/heavy-duty/box)-minted Coolify on a control-plane box. And inside a [box](https://github.com/heavy-duty/box)-minted
guest, the same verb converges the **box tenants** — claude-box, codex-box, guest, the same verb converges the **box tenants** — claude-box, codex-box,
grok-box, staging-box — from thin, creds-free seeds (see *the box tenants* grok-box, kimi-box, staging-box — from thin, creds-free seeds (see *the box tenants*
below). below).
Philosophy (shared with [box](https://github.com/heavy-duty/box)): Philosophy (shared with [box](https://github.com/heavy-duty/box)):
@ -91,7 +91,7 @@ itself is untouched — what bootstrap converged stays converged.
Run as root on the fresh box (over SSH). Convergent — safe to re-run; a Run as root on the fresh box (over SSH). Convergent — safe to re-run; a
second run changes nothing. (The box TENANT roles — `claude-box`, `codex-box`, second run changes nothing. (The box TENANT roles — `claude-box`, `codex-box`,
`grok-box`, `staging-box` — share the verb but are their own family; the `grok-box`, `kimi-box`, `staging-box` — share the verb but are their own family; the
`-box` suffix says so. See *the box tenants* below.) `-box` suffix says so. See *the box tenants* below.)
```sh ```sh
@ -167,7 +167,7 @@ trait), and every other way that step can fail lands in apply's existing
refusal at the end. refusal at the end.
`--users` does **not** reach the box TENANT roles (`claude-box`, `codex-box`, `--users` does **not** reach the box TENANT roles (`claude-box`, `codex-box`,
`grok-box`, `staging-box`). A tenant is a box-minted *guest*: box auto-runs its bootstrap at `grok-box`, `kimi-box`, `staging-box`). A tenant is a box-minted *guest*: box auto-runs its bootstrap at
mint, non-interactively, with no file to hand it; the guest never joins the mint, non-interactively, with no file to hand it; the guest never joins the
tailnet and has no SSH door of its own — you enter with `box shell`, gated by tailnet and has no SSH door of its own — you enter with `box shell`, gated by
the **host's** `incus` grants, which the host's own users file already the **host's** `incus` grants, which the host's own users file already
@ -382,13 +382,13 @@ unattended VM-host appliance) — and `workstation` is the machine at the keyboa
end of all the SSH connections: `root-door=closed`, `join=login`, entering the end of all the SSH connections: `root-door=closed`, `join=login`, entering the
tailnet as *your* device rather than the fleet's. tailnet as *your* device rather than the fleet's.
### `rig bootstrap <claude-box|codex-box|grok-box|staging-box>` — the box tenants ### `rig bootstrap <claude-box|codex-box|grok-box|kimi-box|staging-box>` — the box tenants
Run as root, **inside** a [box](https://github.com/heavy-duty/box)-minted Run as root, **inside** a [box](https://github.com/heavy-duty/box)-minted
guest. Convergent — safe to re-run; a second run changes nothing. guest. Convergent — safe to re-run; a second run changes nothing.
```sh ```sh
rig bootstrap claude-box # or codex-box, grok-box — the agent tenants rig bootstrap claude-box # or codex-box, grok-box, kimi-box — the agent tenants
rig bootstrap staging-box # the server tenant (docker + sshd hardening) rig bootstrap staging-box # the server tenant (docker + sshd hardening)
rig bootstrap claude-box --user dev # when the seed's BOX_USER differs rig bootstrap claude-box --user dev # when the seed's BOX_USER differs
``` ```
@ -411,6 +411,7 @@ holds the whole per-tenant table), not four hand-maintained scripts:
| `claude-box` | `claude` | the agent toolbelt (git, gh, tmux, ripgrep, jq, age, unzip, build-essential), docker, node 22, the Claude Code CLI on the system PATH, zsh + oh-my-zsh, and `~/.claude/CLAUDE.md` | | `claude-box` | `claude` | the agent toolbelt (git, gh, tmux, ripgrep, jq, age, unzip, build-essential), docker, node 22, the Claude Code CLI on the system PATH, zsh + oh-my-zsh, and `~/.claude/CLAUDE.md` |
| `codex-box` | `codex` | the toolbelt, docker, node 22, `@openai/codex` on the system PATH, and `~/.codex/AGENTS.md` | | `codex-box` | `codex` | the toolbelt, docker, node 22, `@openai/codex` on the system PATH, and `~/.codex/AGENTS.md` |
| `grok-box` | `grok` | the toolbelt, docker, the grok CLI on the system PATH, and `~/.grok/AGENTS.md` | | `grok-box` | `grok` | the toolbelt, docker, the grok CLI on the system PATH, and `~/.grok/AGENTS.md` |
| `kimi-box` | `kimi` | the toolbelt, docker, the kimi CLI (uv-managed) on the system PATH, and `~/.kimi/AGENTS.md` |
| `staging-box` | `ops` | box#69's server posture: docker + the same sshd hardening the machine roles get (shared `lib/sshd.sh`, `root-door=open` acceptance) | | `staging-box` | `ops` | box#69's server posture: docker + the same sshd hardening the machine roles get (shared `lib/sshd.sh`, `root-door=open` acceptance) |
**The role carries the suffix; the user does not.** A tenant user is the **The role carries the suffix; the user does not.** A tenant user is the

View file

@ -33,10 +33,10 @@ commands:
tenant roles); custom and workstation take none. join=login tenant roles); custom and workstation take none. join=login
(workstation) needs no key: interactive login, node must come up (workstation) needs no key: interactive login, node must come up
untagged. Run as root. untagged. Run as root.
bootstrap <claude-box|codex-box|grok-box|staging-box> [--user <name>] bootstrap <claude-box|codex-box|grok-box|kimi-box|staging-box> [--user <name>]
The box TENANT roles: converge a box-minted guest. The '-box' suffix The box TENANT roles: converge a box-minted guest. The '-box' suffix
names the family (a guest, vs the '-server' machine roles above). names the family (a guest, vs the '-server' machine roles above).
claude-box|codex-box|grok-box land the agent toolbelt (git, gh, tmux, docker), the agent's CLI on claude-box|codex-box|grok-box|kimi-box land the agent toolbelt (git, gh, tmux, docker), the agent's CLI on
the system PATH, and the agent-context file (with the box#80 guard: the system PATH, and the agent-context file (with the box#80 guard:
never run box setup-host inside a box). staging-box lands box#69's never run box setup-host inside a box). staging-box lands box#69's
server posture — docker + sshd hardening; its tailnet join stays server posture — docker + sshd hardening; its tailnet join stays

View file

@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# rig bootstrap <claude-box|codex-box|grok-box|staging-box> — the box TENANT # rig bootstrap <claude-box|codex-box|grok-box|kimi-box|staging-box> — the box TENANT
# roles ('-box' names the family: a guest, vs the '-server' machine roles): what a # roles ('-box' names the family: a guest, vs the '-server' machine roles): what a
# box-minted guest becomes (issue #31). box mints the thin, creds-free seed # box-minted guest becomes (issue #31). box mints the thin, creds-free seed
# (base image, user, rig preinstalled — heavy-duty/box#81); rig converges the # (base image, user, rig preinstalled — heavy-duty/box#81); rig converges the
@ -32,13 +32,13 @@ die() { printf 'rig-bootstrap: ERROR: %s\n' "$1" >&2; exit "${2:-1}"; }
usage() { usage() {
cat <<'EOF' cat <<'EOF'
usage: rig bootstrap <claude-box|codex-box|grok-box|staging-box> [--user <name>] usage: rig bootstrap <claude-box|codex-box|grok-box|kimi-box|staging-box> [--user <name>]
Box TENANT roles — what a box-minted guest becomes. box mints the thin, Box TENANT roles — what a box-minted guest becomes. box mints the thin,
creds-free seed (base image, user, rig preinstalled); this converges the creds-free seed (base image, user, rig preinstalled); this converges the
tenant on top, and re-runs converge an existing box to a new spec. tenant on top, and re-runs converge an existing box to a new spec.
claude-box|codex-box|grok-box claude-box|codex-box|grok-box|kimi-box
the agent tenants: base tooling (git, gh, tmux, …), the agent tenants: base tooling (git, gh, tmux, …),
docker, the agent's CLI on the system PATH, and the docker, the agent's CLI on the system PATH, and the
agent-context file — including the box#80 guard: never agent-context file — including the box#80 guard: never
@ -63,10 +63,10 @@ EOF
# --- args (validated before the root check, so errors are testable) --------- # --- args (validated before the root check, so errors are testable) ---------
ROLE="${1:-}" ROLE="${1:-}"
case "$ROLE" in case "$ROLE" in
claude-box|codex-box|grok-box|staging-box) shift ;; claude-box|codex-box|grok-box|kimi-box|staging-box) shift ;;
-h|--help) usage; exit 0 ;; -h|--help) usage; exit 0 ;;
"") usage >&2; die "tenant role required (claude-box|codex-box|grok-box|staging-box)" 2 ;; "") usage >&2; die "tenant role required (claude-box|codex-box|grok-box|kimi-box|staging-box)" 2 ;;
*) die "unknown tenant role: $ROLE (want claude-box|codex-box|grok-box|staging-box)" 2 ;; *) die "unknown tenant role: $ROLE (want claude-box|codex-box|grok-box|kimi-box|staging-box)" 2 ;;
esac esac
TENANT_USER="$(tenant_user "$ROLE")" TENANT_USER="$(tenant_user "$ROLE")"
@ -183,7 +183,7 @@ case "$ROLE" in
# The claude-box tenant keeps zsh (its shell UX ships with the box); the # The claude-box tenant keeps zsh (its shell UX ships with the box); the
# remaining list is the shared agent toolbelt the templates carried. # remaining list is the shared agent toolbelt the templates carried.
apt-get install -y -qq git gh curl ca-certificates gnupg ripgrep jq tmux age unzip build-essential zsh ;; apt-get install -y -qq git gh curl ca-certificates gnupg ripgrep jq tmux age unzip build-essential zsh ;;
codex-box|grok-box) codex-box|grok-box|kimi-box)
apt-get install -y -qq git gh curl ca-certificates gnupg ripgrep jq tmux age unzip build-essential ;; apt-get install -y -qq git gh curl ca-certificates gnupg ripgrep jq tmux age unzip build-essential ;;
staging-box) staging-box)
# openssh-server: the hardening drop-in below targets /etc/ssh/sshd_config.d/, # openssh-server: the hardening drop-in below targets /etc/ssh/sshd_config.d/,
@ -291,6 +291,20 @@ case "$ROLE" in
else else
log "grok CLI already installed" log "grok CLI already installed"
fi ;; fi ;;
kimi-box)
# The OFFICIAL installer (code.kimi.com/install.sh): a uv-managed Python
# tool (kimi-cli), landing `kimi` in ~/.local/bin — uv's tool bin — with
# uv bringing its own managed CPython, so no apt python pin here (the
# node section above stays claude/codex-only for the same reason). Run AS
# the tenant user, never root: grok's lesson — a root-owned install under
# a 0700 home is a CLI that exists and cannot run.
CLI=kimi CLI_SRC="$TENANT_HOME/.local/bin/kimi"
if [ ! -e "$CLI_SRC" ]; then
log "installing the Kimi CLI as ${TENANT_USER}"
runuser -l "$TENANT_USER" -c 'curl -LsSf https://code.kimi.com/install.sh | bash'
else
log "kimi CLI already installed"
fi ;;
staging-box) ;; # no agent lives on the staging-box tenant staging-box) ;; # no agent lives on the staging-box tenant
esac esac
if [ -n "$CLI" ]; then if [ -n "$CLI" ]; then
@ -313,6 +327,8 @@ if [ -n "$CLI" ]; then
append_line_once "$TENANT_HOME/.bashrc" 'export PATH="$(npm prefix -g)/bin:$PATH"' ;; append_line_once "$TENANT_HOME/.bashrc" 'export PATH="$(npm prefix -g)/bin:$PATH"' ;;
grok-box) grok-box)
append_line_once "$TENANT_HOME/.bashrc" 'export PATH="$HOME/.grok/bin:$PATH"' ;; append_line_once "$TENANT_HOME/.bashrc" 'export PATH="$HOME/.grok/bin:$PATH"' ;;
kimi-box)
append_line_once "$TENANT_HOME/.bashrc" 'export PATH="$HOME/.local/bin:$PATH"' ;;
esac esac
fi fi

View file

@ -29,7 +29,7 @@ usage: rig bootstrap <control-plane-server|workload-server|runner-server|
(--users <path> | --no-users) (--users <path> | --no-users)
[--hostname <name>] [--root-door <closed|open>] [--hostname <name>] [--root-door <closed|open>]
[--host <yes|no>] [--join <authkey|login>] [--host <yes|no>] [--join <authkey|login>]
rig bootstrap <claude-box|codex-box|grok-box|staging-box> [--user <name>] rig bootstrap <claude-box|codex-box|grok-box|kimi-box|staging-box> [--user <name>]
(the box TENANT roles — see their own --help; they take (the box TENANT roles — see their own --help; they take
no --users, see below) no --users, see below)
@ -56,7 +56,7 @@ and per-human accounts keep attribution intact for the times someone does go
in. So the complete path is the default path and skipping it is a deliberate in. So the complete path is the default path and skipping it is a deliberate
--no-users, not an omission. --no-users, not an omission.
--users does NOT reach the box TENANT roles (claude-box|codex-box|grok-box|staging-box). A --users does NOT reach the box TENANT roles (claude-box|codex-box|grok-box|kimi-box|staging-box). A
tenant is a box-minted GUEST: box auto-runs its bootstrap at mint, tenant is a box-minted GUEST: box auto-runs its bootstrap at mint,
non-interactively, with no file to hand it; the guest never joins the tailnet non-interactively, with no file to hand it; the guest never joins the tailnet
and has no SSH door of its own — entry is `box shell`, gated by the HOST's and has no SSH door of its own — entry is `box shell`, gated by the HOST's
@ -115,15 +115,15 @@ EOF
ROLE="${1:-}" ROLE="${1:-}"
case "$ROLE" in case "$ROLE" in
control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom) shift ;; control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom) shift ;;
claude-box|codex-box|grok-box|staging-box) claude-box|codex-box|grok-box|kimi-box|staging-box)
# The box TENANT roles (#31) are a different family — guests a box mints, # The box TENANT roles (#31) are a different family — guests a box mints,
# never tailnet machines — and live in their own mechanism, one script # never tailnet machines — and live in their own mechanism, one script
# parameterized per tenant. Dispatched here so `rig bootstrap <role>` # parameterized per tenant. Dispatched here so `rig bootstrap <role>`
# stays the single entrypoint for both families. # stays the single entrypoint for both families.
exec "$HERE/bootstrap-tenant.sh" "$@" ;; exec "$HERE/bootstrap-tenant.sh" "$@" ;;
-h|--help) usage; exit 0 ;; -h|--help) usage; exit 0 ;;
"") usage >&2; die "role required (control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom — or a tenant role: claude-box|codex-box|grok-box|staging-box)" 2 ;; "") usage >&2; die "role required (control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom — or a tenant role: claude-box|codex-box|grok-box|kimi-box|staging-box)" 2 ;;
*) die "unknown role: $ROLE (want control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom — or a tenant role: claude-box|codex-box|grok-box|staging-box)" 2 ;; *) die "unknown role: $ROLE (want control-plane-server|workload-server|runner-server|staging-server|dev-server|workstation|custom — or a tenant role: claude-box|codex-box|grok-box|kimi-box|staging-box)" 2 ;;
esac esac
# Role→traits map — the single place a role's shape is declared (issue #26). # Role→traits map — the single place a role's shape is declared (issue #26).

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Shared parameters for the box TENANT roles (claude-box, codex-box, grok-box, # Shared parameters for the box TENANT roles (claude-box, codex-box, grok-box,
# staging-box) — the '-box' suffix names the FAMILY (a guest a box mints, vs the # kimi-box, staging-box) — the '-box' suffix names the FAMILY (a guest a box mints, vs the
# '-server' machine roles rig converges); see #76. # '-server' machine roles rig converges); see #76.
# sourced by bootstrap-tenant.sh and by the test harness. Pure text→text, no # sourced by bootstrap-tenant.sh and by the test harness. Pure text→text, no
# side effects: the per-tenant differences live HERE, in one table, so the # side effects: the per-tenant differences live HERE, in one table, so the
@ -15,6 +15,7 @@ tenant_user() {
claude-box) printf 'claude' ;; claude-box) printf 'claude' ;;
codex-box) printf 'codex' ;; codex-box) printf 'codex' ;;
grok-box) printf 'grok' ;; grok-box) printf 'grok' ;;
kimi-box) printf 'kimi' ;;
staging-box) printf 'ops' ;; staging-box) printf 'ops' ;;
*) return 1 ;; *) return 1 ;;
esac esac
@ -29,12 +30,18 @@ tenant_context_path() {
claude-box) printf '%s/.claude/CLAUDE.md' "$2" ;; claude-box) printf '%s/.claude/CLAUDE.md' "$2" ;;
codex-box) printf '%s/.codex/AGENTS.md' "$2" ;; codex-box) printf '%s/.codex/AGENTS.md' "$2" ;;
grok-box) printf '%s/.grok/AGENTS.md' "$2" ;; grok-box) printf '%s/.grok/AGENTS.md' "$2" ;;
# kimi documents only PROJECT-level AGENTS.md today (no global file); its
# dotdir is ~/.kimi (config.toml, sessions/, credentials/). The context
# file lands at the <dotdir>/AGENTS.md convention the other CLIs converged
# on, so it is where an operator (or a future global-read) will look — an
# honest placement, not a claim that the CLI auto-loads it.
kimi-box) printf '%s/.kimi/AGENTS.md' "$2" ;;
*) return 1 ;; *) return 1 ;;
esac esac
} }
# render_tenant_context <role> — the agent-context file's content, on stdout. # render_tenant_context <role> — the agent-context file's content, on stdout.
# One renderer for all three agents: only the creds paragraph is per-vendor, # One renderer for all four agents: only the creds paragraph is per-vendor,
# and the box#80 guard note lives HERE once — never copy-pasted per template. # and the box#80 guard note lives HERE once — never copy-pasted per template.
# staging-box renders nothing (return 1): no agent lives there. # staging-box renders nothing (return 1): no agent lives there.
render_tenant_context() { render_tenant_context() {
@ -63,6 +70,13 @@ render_tenant_context() {
operator adds their own credentials (a PAT or `gh auth login`). Never operator adds their own credentials (a PAT or `gh auth login`). Never
assume credentials are present; never ask for or store secrets on disk assume credentials are present; never ask for or store secrets on disk
beyond what the operator sets up.' ;; beyond what the operator sets up.' ;;
kimi-box)
creds='- **Creds-free by default.** The box starts with no Moonshot and no git
credentials. If you need to authenticate, the operator runs `kimi` and
its `/login` flow interactively (Kimi Code OAuth, or an API key). For
git, the operator adds their own credentials (a PAT or `gh auth login`).
Never assume credentials are present; never ask for or store secrets on
disk beyond what the operator sets up.' ;;
*) return 1 ;; *) return 1 ;;
esac esac
cat <<EOF cat <<EOF

View file

@ -492,7 +492,7 @@ else
echo "skip: bootstrap non-root refusals (running as root)" echo "skip: bootstrap non-root refusals (running as root)"
fi fi
# --- box tenant roles (#31/#76): claude-box|codex-box|grok-box|staging-box --- # --- box tenant roles (#31/#76): claude-box|codex-box|grok-box|kimi-box|staging-box ---
# What a box-minted guest becomes — ONE mechanism (bootstrap-tenant.sh), # What a box-minted guest becomes — ONE mechanism (bootstrap-tenant.sh),
# parameterized per tenant through lib/tenant-config.sh, dispatched from # parameterized per tenant through lib/tenant-config.sh, dispatched from
# bootstrap.sh so `rig bootstrap <role>` stays the single entrypoint. The real # bootstrap.sh so `rig bootstrap <role>` stays the single entrypoint. The real
@ -530,7 +530,7 @@ check "tenant: --hostname dies the same way" 2 "have no traits" \
"$ROOT/commands/bootstrap-tenant.sh" staging-box --hostname my-guest "$ROOT/commands/bootstrap-tenant.sh" staging-box --hostname my-guest
# Dispatch: the machine-role entrypoint hands tenant roles to the tenant # Dispatch: the machine-role entrypoint hands tenant roles to the tenant
# mechanism with args intact (--help reaching the TENANT usage proves both). # mechanism with args intact (--help reaching the TENANT usage proves both).
check "bootstrap: tenant roles dispatch through bootstrap.sh" 0 "claude-box|codex-box|grok-box|staging-box" \ check "bootstrap: tenant roles dispatch through bootstrap.sh" 0 "claude-box|codex-box|grok-box|kimi-box|staging-box" \
"$ROOT/commands/bootstrap.sh" claude-box --help "$ROOT/commands/bootstrap.sh" claude-box --help
# The marker guard fires BEFORE the root check (repo precedent: the coolify # The marker guard fires BEFORE the root check (repo precedent: the coolify
# marker warning), so the refusals are provable here off fixture markers. A # marker warning), so the refusals are provable here off fixture markers. A
@ -603,22 +603,26 @@ tpath() { bash -c 'set -euo pipefail
tctx() { bash -c 'set -euo pipefail tctx() { bash -c 'set -euo pipefail
. "$1/commands/lib/tenant-config.sh"; render_tenant_context "$2"' _ "$ROOT" "$1"; } . "$1/commands/lib/tenant-config.sh"; render_tenant_context "$2"' _ "$ROOT" "$1"; }
check "tenant params: agent users are named after their agent" 0 "claude" tuser claude-box check "tenant params: agent users are named after their agent" 0 "claude" tuser claude-box
check "tenant params: kimi's user drops the suffix too" 0 "kimi" tuser kimi-box
check "tenant params: staging's user is box#69's ops" 0 "ops" tuser staging-box check "tenant params: staging's user is box#69's ops" 0 "ops" tuser staging-box
check "tenant params: claude context lands in ~/.claude/CLAUDE.md" 0 "/home/claude/.claude/CLAUDE.md" tpath claude-box /home/claude check "tenant params: claude context lands in ~/.claude/CLAUDE.md" 0 "/home/claude/.claude/CLAUDE.md" tpath claude-box /home/claude
check "tenant params: codex context lands in ~/.codex/AGENTS.md" 0 "/home/codex/.codex/AGENTS.md" tpath codex-box /home/codex check "tenant params: codex context lands in ~/.codex/AGENTS.md" 0 "/home/codex/.codex/AGENTS.md" tpath codex-box /home/codex
check "tenant params: grok context lands in ~/.grok/AGENTS.md" 0 "/home/grok/.grok/AGENTS.md" tpath grok-box /home/grok check "tenant params: grok context lands in ~/.grok/AGENTS.md" 0 "/home/grok/.grok/AGENTS.md" tpath grok-box /home/grok
check "tenant params: kimi context lands in ~/.kimi/AGENTS.md" 0 "/home/kimi/.kimi/AGENTS.md" tpath kimi-box /home/kimi
check "tenant params: staging has no context file" 1 "" tpath staging-box /home/ops check "tenant params: staging has no context file" 1 "" tpath staging-box /home/ops
# The box#80 guard note lives ONCE, in the renderer, and every agent's file # The box#80 guard note lives ONCE, in the renderer, and every agent's file
# carries it — the layering decision's whole point: never per-template again. # carries it — the layering decision's whole point: never per-template again.
check "tenant context: claude carries the box#80 guard" 0 "box setup-host" tctx claude-box check "tenant context: claude carries the box#80 guard" 0 "box setup-host" tctx claude-box
check "tenant context: codex carries the box#80 guard" 0 "box setup-host" tctx codex-box check "tenant context: codex carries the box#80 guard" 0 "box setup-host" tctx codex-box
check "tenant context: grok carries the box#80 guard" 0 "box setup-host" tctx grok-box check "tenant context: grok carries the box#80 guard" 0 "box setup-host" tctx grok-box
check "tenant context: kimi carries the box#80 guard" 0 "box setup-host" tctx kimi-box
check "tenant context: the guard says whose host this is not" 0 "not a host you own" tctx claude-box check "tenant context: the guard says whose host this is not" 0 "not a host you own" tctx claude-box
check "tenant context: the guard cites box#80" 0 "box#80" tctx claude-box check "tenant context: the guard cites box#80" 0 "box#80" tctx claude-box
check "tenant context: the creds-free contract is stated" 0 "Creds-free by default" tctx claude-box check "tenant context: the creds-free contract is stated" 0 "Creds-free by default" tctx claude-box
check "tenant context: claude names /login as the operator's flow" 0 "/login" tctx claude-box check "tenant context: claude names /login as the operator's flow" 0 "/login" tctx claude-box
check "tenant context: codex names its login flow" 0 "login flow (\`codex\`)" tctx codex-box check "tenant context: codex names its login flow" 0 "login flow (\`codex\`)" tctx codex-box
check "tenant context: grok names its login flow" 0 "grok login" tctx grok-box check "tenant context: grok names its login flow" 0 "grok login" tctx grok-box
check "tenant context: kimi names its login flow" 0 "Kimi Code OAuth" tctx kimi-box
check "tenant context: staging renders nothing (no agent lives there)" 1 "" tctx staging-box check "tenant context: staging renders nothing (no agent lives there)" 1 "" tctx staging-box
# Creds-free BY CONSTRUCTION, provable by absence (box#69's grep-refusal # Creds-free BY CONSTRUCTION, provable by absence (box#69's grep-refusal
# idiom): nothing in the tenant mechanism touches the tailnet, prompts, or # idiom): nothing in the tenant mechanism touches the tailnet, prompts, or