From 4bcd2c275b7049589b37825d3c9791724dc1be78 Mon Sep 17 00:00:00 2001 From: Agent Date: Sun, 29 Mar 2026 12:43:18 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20refactor:=20rename=20vault-runner=20?= =?UTF-8?q?=E2=86=92=20runner=20and=20vault-run=20=E2=86=92=20run=20(#43)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 4 +-- AGENTS.md | 4 +-- bin/disinto | 38 ++++++++++---------- formulas/review-pr.toml | 6 ++-- lib/AGENTS.md | 2 +- lib/env.sh | 2 +- vault/AGENTS.md | 2 +- vault/{vault-run-action.sh => run-action.sh} | 14 ++++---- vault/vault-env.sh | 3 ++ vault/vault-fire.sh | 16 ++++----- 10 files changed, 47 insertions(+), 44 deletions(-) rename vault/{vault-run-action.sh => run-action.sh} (89%) diff --git a/.env.example b/.env.example index 762acd3..7ca5ba6 100644 --- a/.env.example +++ b/.env.example @@ -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. diff --git a/AGENTS.md b/AGENTS.md index ffc5561..04a0ac1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/bin/disinto b/bin/disinto index 3151c19..772f0fa 100755 --- a/bin/disinto +++ b/bin/disinto @@ -10,7 +10,7 @@ # disinto shell Shell into the agent container # disinto status Show factory status # disinto secrets Manage encrypted secrets -# disinto vault-run Run action in ephemeral vault container +# disinto run 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 Manage encrypted secrets - disinto vault-run Run action in ephemeral vault container + disinto run Run action in ephemeral runner container Init options: --branch 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 - entrypoint: ["bash", "/home/agent/disinto/vault/vault-run-action.sh"] + # env_file set at runtime by: disinto run --env-file + 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 }" +disinto_run() { + local action_id="${1:?Usage: disinto run }" 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 diff --git a/formulas/review-pr.toml b/formulas/review-pr.toml index b74f1e3..2c02e17 100644 --- a/formulas/review-pr.toml +++ b/formulas/review-pr.toml @@ -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) diff --git a/lib/AGENTS.md b/lib/AGENTS.md index 520440b..7bfc736 100644 --- a/lib/AGENTS.md +++ b/lib/AGENTS.md @@ -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 \" 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 ` — queries Woodpecker directly for CI state, falls back to forge commit status API. `ci_pipeline_number ` — returns the Woodpecker pipeline number for a commit, falls back to parsing forge status `target_url`. `ci_promote ` — 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) | diff --git a/lib/env.sh b/lib/env.sh index d2af00e..92eb676 100755 --- a/lib/env.sh +++ b/lib/env.sh @@ -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 diff --git a/vault/AGENTS.md b/vault/AGENTS.md index 6461064..879e645 100644 --- a/vault/AGENTS.md +++ b/vault/AGENTS.md @@ -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 diff --git a/vault/vault-run-action.sh b/vault/run-action.sh similarity index 89% rename from vault/vault-run-action.sh rename to vault/run-action.sh index 707f3db..b051511 100755 --- a/vault/vault-run-action.sh +++ b/vault/run-action.sh @@ -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 +# Usage: run-action.sh 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="${1:?Usage: run-action.sh }" 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/ diff --git a/vault/vault-env.sh b/vault/vault-env.sh index 79e4176..66b87d1 100644 --- a/vault/vault-env.sh +++ b/vault/vault-env.sh @@ -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" diff --git a/vault/vault-fire.sh b/vault/vault-fire.sh index ad57022..79c1d46 100755 --- a/vault/vault-fire.sh +++ b/vault/vault-fire.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 # =============================================================================