fix: refactor: rename vault-runner → runner and vault-run → run (#43)
This commit is contained in:
parent
9335681a72
commit
4bcd2c275b
10 changed files with 47 additions and 44 deletions
|
|
@ -49,7 +49,7 @@ WOODPECKER_DB_NAME=woodpecker # [CONFIG] Postgres database name
|
|||
|
||||
# ── Vault-only secrets (DO NOT put these in .env) ────────────────────────
|
||||
# These tokens grant access to external systems (GitHub, ClawHub, deploy targets).
|
||||
# They live ONLY in .env.vault.enc and are injected into the ephemeral vault-runner
|
||||
# They live ONLY in .env.vault.enc and are injected into the ephemeral runner
|
||||
# container at fire time (#745). lib/env.sh explicitly unsets them so agents
|
||||
# can never hold them directly — all external actions go through vault dispatch.
|
||||
#
|
||||
|
|
@ -58,7 +58,7 @@ WOODPECKER_DB_NAME=woodpecker # [CONFIG] Postgres database name
|
|||
# (deploy keys) — SSH keys for deployment targets
|
||||
#
|
||||
# To manage vault secrets: disinto secrets edit-vault
|
||||
# See also: vault/vault-run-action.sh, vault/vault-fire.sh
|
||||
# See also: vault/run-action.sh, vault/vault-fire.sh
|
||||
|
||||
# ── Project-specific secrets ──────────────────────────────────────────────
|
||||
# Store all project secrets here so formulas reference env vars, never hardcode.
|
||||
|
|
|
|||
|
|
@ -164,8 +164,8 @@ Humans write these. Agents read and enforce them.
|
|||
| AD-002 | Single-threaded pipeline per project. | One dev issue at a time. No new work while a PR awaits CI or review. Prevents merge conflicts and keeps context clear. |
|
||||
| AD-003 | The runtime creates and destroys, the formula preserves. | Runtime manages worktrees/sessions/temp. Formulas commit knowledge to git before signaling done. |
|
||||
| AD-004 | Event-driven > polling > fixed delays. | Never `waitForTimeout` or hardcoded sleep. Use phase files, webhooks, or poll loops with backoff. |
|
||||
| AD-005 | Secrets via env var indirection, never in issue bodies. | Issue bodies become code. Agent secrets go in `.env.enc`, vault secrets in `.env.vault.enc` (both SOPS-encrypted). Referenced as `$VAR_NAME`. Vault-runner gets only vault secrets; agents get only agent secrets. |
|
||||
| AD-006 | External actions go through vault dispatch, never direct. | Agents build addressables; only the vault exercises them (publishes, deploys, posts). Tokens for external systems (`GITHUB_TOKEN`, `CLAWHUB_TOKEN`, deploy keys) live only in `.env.vault.enc` and are injected into the ephemeral vault-runner container. `lib/env.sh` unsets them so agents never hold them. PRs with direct external actions without vault dispatch get REQUEST_CHANGES. |
|
||||
| AD-005 | Secrets via env var indirection, never in issue bodies. | Issue bodies become code. Agent secrets go in `.env.enc`, vault secrets in `.env.vault.enc` (both SOPS-encrypted). Referenced as `$VAR_NAME`. Runner gets only vault secrets; agents get only agent secrets. |
|
||||
| AD-006 | External actions go through vault dispatch, never direct. | Agents build addressables; only the vault exercises them (publishes, deploys, posts). Tokens for external systems (`GITHUB_TOKEN`, `CLAWHUB_TOKEN`, deploy keys) live only in `.env.vault.enc` and are injected into the ephemeral runner container. `lib/env.sh` unsets them so agents never hold them. PRs with direct external actions without vault dispatch get REQUEST_CHANGES. |
|
||||
|
||||
**Who enforces what:**
|
||||
- **Gardener** checks open backlog issues against ADs during grooming; closes violations with a comment referencing the AD number.
|
||||
|
|
|
|||
38
bin/disinto
38
bin/disinto
|
|
@ -10,7 +10,7 @@
|
|||
# disinto shell Shell into the agent container
|
||||
# disinto status Show factory status
|
||||
# disinto secrets <subcommand> Manage encrypted secrets
|
||||
# disinto vault-run <action-id> Run action in ephemeral vault container
|
||||
# disinto run <action-id> Run action in ephemeral runner container
|
||||
#
|
||||
# Usage:
|
||||
# disinto init https://github.com/user/repo
|
||||
|
|
@ -39,7 +39,7 @@ Usage:
|
|||
disinto shell Shell into the agent container
|
||||
disinto status Show factory status
|
||||
disinto secrets <subcommand> Manage encrypted secrets
|
||||
disinto vault-run <action-id> Run action in ephemeral vault container
|
||||
disinto run <action-id> Run action in ephemeral runner container
|
||||
|
||||
Init options:
|
||||
--branch <name> Primary branch (default: auto-detect)
|
||||
|
|
@ -242,7 +242,7 @@ services:
|
|||
- .env
|
||||
# IMPORTANT: agents get .env only (forge tokens, CI tokens, config).
|
||||
# Vault-only secrets (GITHUB_TOKEN, CLAWHUB_TOKEN, deploy keys) live in
|
||||
# .env.vault.enc and are NEVER injected here — only the vault-runner
|
||||
# .env.vault.enc and are NEVER injected here — only the runner
|
||||
# container receives them at fire time (AD-006, #745).
|
||||
depends_on:
|
||||
- forgejo
|
||||
|
|
@ -250,7 +250,7 @@ services:
|
|||
networks:
|
||||
- disinto-net
|
||||
|
||||
vault-runner:
|
||||
runner:
|
||||
build: ./docker/agents
|
||||
profiles: ["vault"]
|
||||
security_opt:
|
||||
|
|
@ -263,8 +263,8 @@ services:
|
|||
FORGE_URL: http://forgejo:3000
|
||||
DISINTO_CONTAINER: "1"
|
||||
PROJECT_REPO_ROOT: /home/agent/repos/\${PROJECT_NAME:-project}
|
||||
# env_file set at runtime by: disinto vault-run --env-file <tmpfile>
|
||||
entrypoint: ["bash", "/home/agent/disinto/vault/vault-run-action.sh"]
|
||||
# env_file set at runtime by: disinto run --env-file <tmpfile>
|
||||
entrypoint: ["bash", "/home/agent/disinto/vault/run-action.sh"]
|
||||
networks:
|
||||
- disinto-net
|
||||
|
||||
|
|
@ -466,8 +466,8 @@ generate_deploy_pipelines() {
|
|||
if [ ! -f "${wp_dir}/staging.yml" ]; then
|
||||
cat > "${wp_dir}/staging.yml" <<'STAGINGEOF'
|
||||
# .woodpecker/staging.yml — Staging deployment pipeline
|
||||
# Triggered by vault-runner via Woodpecker promote API.
|
||||
# Human approves promotion in vault → vault-runner calls promote → this runs.
|
||||
# Triggered by runner via Woodpecker promote API.
|
||||
# Human approves promotion in vault → runner calls promote → this runs.
|
||||
|
||||
when:
|
||||
event: deployment
|
||||
|
|
@ -498,8 +498,8 @@ STAGINGEOF
|
|||
if [ ! -f "${wp_dir}/production.yml" ]; then
|
||||
cat > "${wp_dir}/production.yml" <<'PRODUCTIONEOF'
|
||||
# .woodpecker/production.yml — Production deployment pipeline
|
||||
# Triggered by vault-runner via Woodpecker promote API.
|
||||
# Human approves promotion in vault → vault-runner calls promote → this runs.
|
||||
# Triggered by runner via Woodpecker promote API.
|
||||
# Human approves promotion in vault → runner calls promote → this runs.
|
||||
|
||||
when:
|
||||
event: deployment
|
||||
|
|
@ -2184,10 +2184,10 @@ EOF
|
|||
esac
|
||||
}
|
||||
|
||||
# ── vault-run command ─────────────────────────────────────────────────────────
|
||||
# ── run command ───────────────────────────────────────────────────────────────
|
||||
|
||||
disinto_vault_run() {
|
||||
local action_id="${1:?Usage: disinto vault-run <action-id>}"
|
||||
disinto_run() {
|
||||
local action_id="${1:?Usage: disinto run <action-id>}"
|
||||
local compose_file="${FACTORY_ROOT}/docker-compose.yml"
|
||||
local vault_enc="${FACTORY_ROOT}/.env.vault.enc"
|
||||
|
||||
|
|
@ -2221,20 +2221,20 @@ disinto_vault_run() {
|
|||
|
||||
echo "Vault secrets decrypted to tmpfile"
|
||||
|
||||
# Run action in ephemeral vault-runner container
|
||||
# Run action in ephemeral runner container
|
||||
local rc=0
|
||||
docker compose -f "$compose_file" \
|
||||
run --rm --env-file "$tmp_env" \
|
||||
vault-runner "$action_id" || rc=$?
|
||||
runner "$action_id" || rc=$?
|
||||
|
||||
# Clean up — secrets gone
|
||||
rm -f "$tmp_env"
|
||||
echo "Vault tmpfile removed"
|
||||
echo "Run tmpfile removed"
|
||||
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
echo "Vault action ${action_id} completed successfully"
|
||||
echo "Run action ${action_id} completed successfully"
|
||||
else
|
||||
echo "Vault action ${action_id} failed (exit ${rc})" >&2
|
||||
echo "Run action ${action_id} failed (exit ${rc})" >&2
|
||||
fi
|
||||
return "$rc"
|
||||
}
|
||||
|
|
@ -2314,7 +2314,7 @@ case "${1:-}" in
|
|||
shell) shift; disinto_shell ;;
|
||||
status) shift; disinto_status "$@" ;;
|
||||
secrets) shift; disinto_secrets "$@" ;;
|
||||
vault-run) shift; disinto_vault_run "$@" ;;
|
||||
run) shift; disinto_run "$@" ;;
|
||||
-h|--help) usage ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ near-duplicate exists, REQUEST_CHANGES and reference the existing item.
|
|||
Agents must NEVER execute external actions directly. Any action that touches
|
||||
an external system (publish, deploy, post, push to external registry, API
|
||||
calls to third-party services) MUST go through vault dispatch — i.e., the
|
||||
agent files a vault item (`$OPS_REPO_ROOT/vault/pending/*.json`) and the vault-runner
|
||||
agent files a vault item (`$OPS_REPO_ROOT/vault/pending/*.json`) and the runner
|
||||
container executes it with injected secrets.
|
||||
|
||||
Scan the diff for these patterns:
|
||||
|
|
@ -129,7 +129,7 @@ Scan the diff for these patterns:
|
|||
If ANY of these patterns appear in agent code (scripts in `dev/`, `action/`,
|
||||
`planner/`, `gardener/`, `supervisor/`, `predictor/`, `review/`, `formulas/`,
|
||||
`lib/`) WITHOUT routing through vault dispatch (`$OPS_REPO_ROOT/vault/pending/`, `vault-fire.sh`,
|
||||
`vault-run-action.sh`), **REQUEST_CHANGES**.
|
||||
`run-action.sh`), **REQUEST_CHANGES**.
|
||||
|
||||
Explain that external actions must use vault dispatch per AD-006. The agent
|
||||
should file a vault item instead of executing directly.
|
||||
|
|
@ -137,7 +137,7 @@ should file a vault item instead of executing directly.
|
|||
**Exceptions** (do NOT flag these):
|
||||
- Code inside `vault/` — the vault system itself is allowed to handle secrets
|
||||
- References in comments or documentation explaining the architecture
|
||||
- `bin/disinto` setup commands that manage `.env.vault.enc`
|
||||
- `bin/disinto` setup commands that manage `.env.vault.enc` and the `run` subcommand
|
||||
- Local operations (git push to forge, forge API calls with `FORGE_TOKEN`)
|
||||
|
||||
## 6. Re-review (if previous review is provided)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ sourced as needed.
|
|||
|
||||
| File | What it provides | Sourced by |
|
||||
|---|---|---|
|
||||
| `lib/env.sh` | Loads `.env`, sets `FACTORY_ROOT`, exports project config (`FORGE_REPO`, `PROJECT_NAME`, etc.), defines `log()`, `forge_api()`, `forge_api_all()` (accepts optional second TOKEN parameter, defaults to `$FORGE_TOKEN`), `woodpecker_api()`, `wpdb()`, `memory_guard()` (skips agent if RAM < threshold). Auto-loads project TOML if `PROJECT_TOML` is set. Exports per-agent tokens (`FORGE_PLANNER_TOKEN`, `FORGE_GARDENER_TOKEN`, `FORGE_VAULT_TOKEN`, `FORGE_SUPERVISOR_TOKEN`, `FORGE_PREDICTOR_TOKEN`, `FORGE_ACTION_TOKEN`) — each falls back to `$FORGE_TOKEN` if not set. **Vault-only token guard (AD-006)**: `unset GITHUB_TOKEN CLAWHUB_TOKEN` so agents never hold external-action tokens — only the vault-runner container receives them. **Container note**: when `DISINTO_CONTAINER=1`, `.env` is NOT re-sourced — compose already injects env vars (including `FORGE_URL=http://forgejo:3000`) and re-sourcing would clobber them. | Every agent |
|
||||
| `lib/env.sh` | Loads `.env`, sets `FACTORY_ROOT`, exports project config (`FORGE_REPO`, `PROJECT_NAME`, etc.), defines `log()`, `forge_api()`, `forge_api_all()` (accepts optional second TOKEN parameter, defaults to `$FORGE_TOKEN`), `woodpecker_api()`, `wpdb()`, `memory_guard()` (skips agent if RAM < threshold). Auto-loads project TOML if `PROJECT_TOML` is set. Exports per-agent tokens (`FORGE_PLANNER_TOKEN`, `FORGE_GARDENER_TOKEN`, `FORGE_VAULT_TOKEN`, `FORGE_SUPERVISOR_TOKEN`, `FORGE_PREDICTOR_TOKEN`, `FORGE_ACTION_TOKEN`) — each falls back to `$FORGE_TOKEN` if not set. **Vault-only token guard (AD-006)**: `unset GITHUB_TOKEN CLAWHUB_TOKEN` so agents never hold external-action tokens — only the runner container receives them. **Container note**: when `DISINTO_CONTAINER=1`, `.env` is NOT re-sourced — compose already injects env vars (including `FORGE_URL=http://forgejo:3000`) and re-sourcing would clobber them. | Every agent |
|
||||
| `lib/ci-helpers.sh` | `ci_passed()` — returns 0 if CI state is "success" (or no CI configured). `ci_required_for_pr()` — returns 0 if PR has code files (CI required), 1 if non-code only (CI not required). `is_infra_step()` — returns 0 if a single CI step failure matches infra heuristics (clone/git exit 128, any exit 137, log timeout patterns). `classify_pipeline_failure()` — returns "infra \<reason>" if any failed Woodpecker step matches infra heuristics via `is_infra_step()`, else "code". `ensure_priority_label()` — looks up (or creates) the `priority` label and returns its ID; caches in `_PRIORITY_LABEL_ID`. `ci_commit_status <sha>` — queries Woodpecker directly for CI state, falls back to forge commit status API. `ci_pipeline_number <sha>` — returns the Woodpecker pipeline number for a commit, falls back to parsing forge status `target_url`. `ci_promote <repo_id> <pipeline_num> <environment>` — promotes a pipeline to a named Woodpecker environment (vault-gated deployment: vault approves, vault-fire calls this). | dev-poll, review-poll, review-pr, supervisor-poll |
|
||||
| `lib/ci-debug.sh` | CLI tool for Woodpecker CI: `list`, `status`, `logs`, `failures` subcommands. Not sourced — run directly. | Humans / dev-agent (tool access) |
|
||||
| `lib/load-project.sh` | Parses a `projects/*.toml` file into env vars (`PROJECT_NAME`, `FORGE_REPO`, `WOODPECKER_REPO_ID`, monitoring toggles, mirror config, etc.). | env.sh (when `PROJECT_TOML` is set), supervisor-poll (per-project iteration) |
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ export CLAUDE_TIMEOUT="${CLAUDE_TIMEOUT:-7200}"
|
|||
|
||||
# Vault-only token guard (#745): external-action tokens (GITHUB_TOKEN, CLAWHUB_TOKEN)
|
||||
# must NEVER be available to agents. They live in .env.vault.enc and are injected
|
||||
# only into the ephemeral vault-runner container at fire time. Unset them here so
|
||||
# only into the ephemeral runner container at fire time. Unset them here so
|
||||
# even an accidental .env inclusion cannot leak them into agent sessions.
|
||||
unset GITHUB_TOKEN 2>/dev/null || true
|
||||
unset CLAWHUB_TOKEN 2>/dev/null || true
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ needed — the human reviews and publishes directly.
|
|||
**Key files**:
|
||||
- `vault/vault-poll.sh` — Processes pending items: retry approved, auto-reject after 48h timeout, invoke vault-agent for JSON actions, notify human for procurement requests
|
||||
- `vault/vault-agent.sh` — Classifies and routes pending JSON actions via `claude -p`: auto-approve, auto-reject, or escalate to human
|
||||
- `vault/vault-env.sh` — Shared env setup for vault sub-scripts: sources `lib/env.sh`, overrides `FORGE_TOKEN` with `FORGE_VAULT_TOKEN`, sets `VAULT_TOKEN` for vault-runner container
|
||||
- `vault/vault-env.sh` — Shared env setup for vault sub-scripts: sources `lib/env.sh`, overrides `FORGE_TOKEN` with `FORGE_VAULT_TOKEN`, sets `VAULT_TOKEN` for runner container
|
||||
- `formulas/run-vault.toml` — Source-of-truth formula for the vault agent's classification and routing logic
|
||||
- `vault/vault-fire.sh` — Executes an approved action (JSON) in an **ephemeral Docker container** with vault-only secrets injected (GITHUB_TOKEN, CLAWHUB_TOKEN — never exposed to agents). For deployment actions, calls `lib/ci-helpers.sh:ci_promote()` to gate production promotes via Woodpecker environments. Writes `$OPS_REPO_ROOT/RESOURCES.md` entry for procurement MD approvals.
|
||||
- `vault/vault-reject.sh` — Marks a JSON action as rejected
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
#!/usr/bin/env bash
|
||||
# vault-run-action.sh — Execute an action inside the ephemeral vault-runner container
|
||||
# run-action.sh — Execute an action inside the ephemeral runner container
|
||||
#
|
||||
# This script is the entrypoint for the vault-runner container. It runs with
|
||||
# This script is the entrypoint for the runner container. It runs with
|
||||
# vault secrets injected as environment variables (GITHUB_TOKEN, CLAWHUB_TOKEN,
|
||||
# deploy keys, etc.) and dispatches to the appropriate action handler.
|
||||
#
|
||||
# The vault-runner container is ephemeral: it starts, runs the action, and is
|
||||
# The runner container is ephemeral: it starts, runs the action, and is
|
||||
# destroyed. Secrets exist only in container memory, never on disk.
|
||||
#
|
||||
# Usage: vault-run-action.sh <action-id>
|
||||
# Usage: run-action.sh <action-id>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VAULT_SCRIPT_DIR="${DISINTO_VAULT_DIR:-/home/agent/disinto/vault}"
|
||||
OPS_VAULT_DIR="${DISINTO_OPS_VAULT_DIR:-${VAULT_SCRIPT_DIR}}"
|
||||
LOGFILE="${VAULT_SCRIPT_DIR}/vault.log"
|
||||
ACTION_ID="${1:?Usage: vault-run-action.sh <action-id>}"
|
||||
ACTION_ID="${1:?Usage: run-action.sh <action-id>}"
|
||||
|
||||
log() {
|
||||
printf '[%s] vault-runner: %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" >> "$LOGFILE" 2>/dev/null || \
|
||||
printf '[%s] vault-runner: %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" >&2
|
||||
printf '[%s] runner: %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" >> "$LOGFILE" 2>/dev/null || \
|
||||
printf '[%s] runner: %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$*" >&2
|
||||
}
|
||||
|
||||
# Find action file in approved/
|
||||
|
|
@ -7,3 +7,6 @@
|
|||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/lib/env.sh"
|
||||
# Use vault-bot's own Forgejo identity
|
||||
FORGE_TOKEN="${FORGE_VAULT_TOKEN:-${FORGE_TOKEN}}"
|
||||
|
||||
# Set entrypoint for runner container
|
||||
export VAULT_RUNNER_ENTRYPOINT="run-action.sh"
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
#
|
||||
# Handles two pipelines:
|
||||
# A. Action gating (*.json): pending/ → approved/ → fired/
|
||||
# Execution delegated to ephemeral vault-runner container via disinto vault-run.
|
||||
# The vault-runner gets vault secrets (.env.vault.enc); this script does NOT.
|
||||
# Execution delegated to ephemeral runner container via disinto run.
|
||||
# The runner gets vault secrets (.env.vault.enc); this script does NOT.
|
||||
# B. Procurement (*.md): approved/ → fired/ (writes RESOURCES.md entry)
|
||||
#
|
||||
# If item is in pending/, moves to approved/ first.
|
||||
|
|
@ -100,7 +100,7 @@ if [ "$IS_PROCUREMENT" = true ]; then
|
|||
fi
|
||||
|
||||
# =============================================================================
|
||||
# Pipeline B: Action gating — delegate to ephemeral vault-runner container
|
||||
# Pipeline B: Action gating — delegate to ephemeral runner container
|
||||
# =============================================================================
|
||||
ACTION_TYPE=$(jq -r '.type // ""' < "$ACTION_FILE")
|
||||
ACTION_SOURCE=$(jq -r '.source // ""' < "$ACTION_FILE")
|
||||
|
|
@ -110,19 +110,19 @@ if [ -z "$ACTION_TYPE" ]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
log "$ACTION_ID: firing type=$ACTION_TYPE source=$ACTION_SOURCE via vault-runner"
|
||||
log "$ACTION_ID: firing type=$ACTION_TYPE source=$ACTION_SOURCE via runner"
|
||||
|
||||
FIRE_EXIT=0
|
||||
|
||||
# Delegate execution to the ephemeral vault-runner container.
|
||||
# The vault-runner gets vault secrets (.env.vault.enc) injected at runtime;
|
||||
# Delegate execution to the ephemeral runner container.
|
||||
# The runner gets vault secrets (.env.vault.enc) injected at runtime;
|
||||
# this host process never sees those secrets.
|
||||
if [ -f "${FACTORY_ROOT}/.env.vault.enc" ] && [ -f "${FACTORY_ROOT}/docker-compose.yml" ]; then
|
||||
bash "${FACTORY_ROOT}/bin/disinto" vault-run "$ACTION_ID" >> "$LOGFILE" 2>&1 || FIRE_EXIT=$?
|
||||
bash "${FACTORY_ROOT}/bin/disinto" run "$ACTION_ID" >> "$LOGFILE" 2>&1 || FIRE_EXIT=$?
|
||||
else
|
||||
# Fallback for bare-metal or pre-migration setups: run action handler directly
|
||||
log "$ACTION_ID: no .env.vault.enc or docker-compose.yml — running action directly"
|
||||
bash "${SCRIPT_DIR}/vault-run-action.sh" "$ACTION_ID" >> "$LOGFILE" 2>&1 || FIRE_EXIT=$?
|
||||
bash "${SCRIPT_DIR}/run-action.sh" "$ACTION_ID" >> "$LOGFILE" 2>&1 || FIRE_EXIT=$?
|
||||
fi
|
||||
|
||||
# =============================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue