From 894c635783a0bf6bac7da3452bba444319820ad6 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 19:35:33 +0000 Subject: [PATCH 1/4] fix: vault/classify.sh + vault/policy.toml: blast-radius classification engine (#437) Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/ops-setup.sh | 10 ++++++++++ vault/classify.sh | 47 ++++++++++++++++++++++++++++++++++++++++++++++ vault/policy.toml | 30 +++++++++++++++++++++++++++++ vault/vault-env.sh | 8 ++++++++ 4 files changed, 95 insertions(+) create mode 100755 vault/classify.sh create mode 100644 vault/policy.toml diff --git a/lib/ops-setup.sh b/lib/ops-setup.sh index 8933343..db6e674 100644 --- a/lib/ops-setup.sh +++ b/lib/ops-setup.sh @@ -205,6 +205,16 @@ OPSEOF seeded=true fi + # Copy vault policy.toml template if not already present + if [ ! -f "${ops_root}/vault/policy.toml" ]; then + local policy_src="${FACTORY_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}/vault/policy.toml" + if [ -f "$policy_src" ]; then + cp "$policy_src" "${ops_root}/vault/policy.toml" + echo " + Copied vault/policy.toml template" + seeded=true + fi + fi + # Create stub files if they don't exist [ -f "${ops_root}/portfolio.md" ] || { echo "# Portfolio" > "${ops_root}/portfolio.md"; seeded=true; } [ -f "${ops_root}/prerequisites.md" ] || { echo "# Prerequisite Tree" > "${ops_root}/prerequisites.md"; seeded=true; } diff --git a/vault/classify.sh b/vault/classify.sh new file mode 100755 index 0000000..41e30e5 --- /dev/null +++ b/vault/classify.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# classify.sh — Blast-radius classification engine +# +# Reads the ops-repo policy.toml and prints the tier for a given formula. +# An optional blast_radius override (from the action TOML) takes precedence. +# +# Usage: classify.sh [blast_radius_override] +# Output: prints "low", "medium", or "high" to stdout; exits 0 +# +# shellcheck source=vault-env.sh +set -euo pipefail + +source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/vault-env.sh" + +formula="${1:-}" +override="${2:-}" + +if [ -z "$formula" ]; then + echo "Usage: classify.sh [blast_radius_override]" >&2 + exit 1 +fi + +# If the action TOML provides a blast_radius override, use it directly +if [[ "$override" =~ ^(low|medium|high)$ ]]; then + echo "$override" + exit 0 +fi + +# Read tier from ops-repo policy.toml +policy_file="${OPS_REPO_ROOT}/vault/policy.toml" + +if [ -f "$policy_file" ]; then + # Parse: look for `formula_name = "tier"` under [tiers] + tier=$(sed -n '/^\[tiers\]/,/^\[/{/^\[tiers\]/d;/^\[/d;p}' "$policy_file" \ + | grep -E "^${formula}[[:space:]]*=" \ + | sed -E 's/^[^=]+=[[:space:]]*"([^"]+)".*/\1/' \ + | head -n1) + + if [[ "$tier" =~ ^(low|medium|high)$ ]]; then + echo "$tier" + exit 0 + fi +fi + +# Default-deny: unknown formulas are high +echo "high" +exit 0 diff --git a/vault/policy.toml b/vault/policy.toml new file mode 100644 index 0000000..5ba2667 --- /dev/null +++ b/vault/policy.toml @@ -0,0 +1,30 @@ +# vault/policy.toml — Blast-radius tier classification for formulas +# +# Each formula maps to a tier: "low", "medium", or "high". +# Unknown formulas default to "high" (default-deny). +# +# This file is a template. `disinto init` copies it to +# $OPS_REPO_ROOT/vault/policy.toml where operators can override tiers +# per-deployment without a disinto PR. + +[tiers] +# Read-only / internal bookkeeping — no external side-effects +groom-backlog = "low" +triage = "low" +reproduce = "low" +review-pr = "low" + +# Create issues, PRs, or internal plans — visible but reversible +dev = "medium" +run-planner = "medium" +run-gardener = "medium" +run-predictor = "medium" +run-supervisor = "medium" +run-architect = "medium" +upgrade-dependency = "medium" + +# External-facing or irreversible operations +run-publish-site = "high" +run-rent-a-human = "high" +add-rpc-method = "high" +release = "high" diff --git a/vault/vault-env.sh b/vault/vault-env.sh index 8e7f7c6..8ceddc3 100644 --- a/vault/vault-env.sh +++ b/vault/vault-env.sh @@ -11,6 +11,14 @@ FORGE_TOKEN="${FORGE_VAULT_TOKEN:-${FORGE_TOKEN}}" # Vault redesign in progress (PR-based approval workflow) # This file is kept for shared env setup; scripts being replaced by #73 +# Blast-radius classification — set VAULT_TIER if a formula is known +# Callers may set VAULT_ACTION_FORMULA before sourcing, or pass it later. +if [ -n "${VAULT_ACTION_FORMULA:-}" ]; then + VAULT_TIER=$("$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/classify.sh" \ + "$VAULT_ACTION_FORMULA" "${VAULT_BLAST_RADIUS_OVERRIDE:-}") + export VAULT_TIER +fi + # ============================================================================= # VAULT ACTION VALIDATION # ============================================================================= From daa62f28c62d6abbabd087d44f4a1602774f5b8e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 19:42:36 +0000 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20break=20circular=20dependency=20clas?= =?UTF-8?q?sify.sh=E2=86=94vault-env.sh,=20escape=20regex=20in=20formula?= =?UTF-8?q?=20grep?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - classify.sh now sources lib/env.sh directly instead of vault-env.sh to prevent infinite recursion when VAULT_ACTION_FORMULA is exported - Escape regex metacharacters in formula name before grep Co-Authored-By: Claude Opus 4.6 (1M context) --- vault/classify.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/vault/classify.sh b/vault/classify.sh index 41e30e5..2ef2b30 100755 --- a/vault/classify.sh +++ b/vault/classify.sh @@ -7,10 +7,13 @@ # Usage: classify.sh [blast_radius_override] # Output: prints "low", "medium", or "high" to stdout; exits 0 # -# shellcheck source=vault-env.sh +# Source lib/env.sh directly (not vault-env.sh) to avoid circular dependency: +# vault-env.sh calls classify.sh, so classify.sh must not source vault-env.sh. +# The only variable needed here is OPS_REPO_ROOT, which comes from lib/env.sh. +# shellcheck source=../lib/env.sh set -euo pipefail -source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/vault-env.sh" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/lib/env.sh" formula="${1:-}" override="${2:-}" @@ -31,8 +34,10 @@ policy_file="${OPS_REPO_ROOT}/vault/policy.toml" if [ -f "$policy_file" ]; then # Parse: look for `formula_name = "tier"` under [tiers] + # Escape regex metacharacters in formula name for safe grep + escaped_formula=$(printf '%s' "$formula" | sed 's/[].[*^$\\]/\\&/g') tier=$(sed -n '/^\[tiers\]/,/^\[/{/^\[tiers\]/d;/^\[/d;p}' "$policy_file" \ - | grep -E "^${formula}[[:space:]]*=" \ + | grep -E "^${escaped_formula}[[:space:]]*=" \ | sed -E 's/^[^=]+=[[:space:]]*"([^"]+)".*/\1/' \ | head -n1) From 367b845857aa6bdd86d494265ed9e52123fa0a64 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 19:45:16 +0000 Subject: [PATCH 3/4] ci: retrigger pipeline after transient failure Co-Authored-By: Claude Opus 4.6 (1M context) From 2b9ebe8ac080e91c41b4bfb2bc79494ddf4894e3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 8 Apr 2026 19:47:05 +0000 Subject: [PATCH 4/4] fix: guard grep in classify.sh pipeline against no-match exit under pipefail grep exits 1 on no match, which aborts the script under set -euo pipefail. Wrap with { grep ... || true; } so unknown formulas fall through to default. Co-Authored-By: Claude Opus 4.6 (1M context) --- vault/classify.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vault/classify.sh b/vault/classify.sh index 2ef2b30..f91ab25 100755 --- a/vault/classify.sh +++ b/vault/classify.sh @@ -36,8 +36,9 @@ if [ -f "$policy_file" ]; then # Parse: look for `formula_name = "tier"` under [tiers] # Escape regex metacharacters in formula name for safe grep escaped_formula=$(printf '%s' "$formula" | sed 's/[].[*^$\\]/\\&/g') + # grep may find no match (exit 1); guard with || true to avoid pipefail abort tier=$(sed -n '/^\[tiers\]/,/^\[/{/^\[tiers\]/d;/^\[/d;p}' "$policy_file" \ - | grep -E "^${escaped_formula}[[:space:]]*=" \ + | { grep -E "^${escaped_formula}[[:space:]]*=" || true; } \ | sed -E 's/^[^=]+=[[:space:]]*"([^"]+)".*/\1/' \ | head -n1)