Compare commits

...

3 Commits

Author SHA1 Message Date
Svrnty
30d586e79e chore(bypass): Wave 7 schema-migration transition — drift expected per W7-DRIFT-RESIDUAL
enforcement-bypass: schema-migration — Wave 7 v1→v2 schema migration in flight; install.sh F2 denylist landed but additive external_dirs builtin-path additions deferred to Wave 8 per sot/01-ROADMAP/WAVE7-SPRINT-2026-05-25.md §"Wave 7 D7 — runtime verification findings"

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:11:11 -04:00
Svrnty
6f5ca6573c feat(install): Wave 7 D6 — steev install.sh disclosure→runtime + subrepo hook — sprint 2026-05-25 2026-05-24 16:59:17 -04:00
Svrnty
b85b266dcb feat(disclosure): Wave 7 D2 — steev schema v2 — sprint 2026-05-25 2026-05-24 16:53:16 -04:00
3 changed files with 224 additions and 4 deletions

View File

@ -4,8 +4,8 @@ tier: T2
status: active
owner: jp
source: generated
last_reviewed: 2026-05-24
review_by: 2026-08-22
last_reviewed: 2026-05-25
review_by: 2026-08-23
depends_on:
- disclosure-schema
- profile-distribution-protocol
@ -15,7 +15,7 @@ auto_regen_cmd: "yq '.disclosure' manifest.yaml | <renderer-script>"
# `steev` — Disclosure
> Live as of `2026-05-24`. Source: `steev/manifest.yaml → disclosure:` block. Pre-push hook check 6 (curator/lib/pre-push.sh) enforces this == live `hermes -p steev` runtime.
> Live as of `2026-05-25`. Disclosure schema v2 (manifest `disclosure.schema_version: 2` — adds `external_orchestrators` per DISCLOSURE-SCHEMA §4.6). Source: `steev/manifest.yaml → disclosure:` block. Pre-push hook check 6 (curator/lib/pre-push.sh) enforces this == live `hermes -p steev` runtime.
## §1 Identity
@ -43,6 +43,7 @@ auto_regen_cmd: "yq '.disclosure' manifest.yaml | <renderer-script>"
| `inherit_mcp_toolsets` | `false` | **CLAUDE.md hard-rule fix.** Closes Wave-1 finding: `bte` MCP silently leaked from host. `bte` = Plan B marketing platform — forbidden to steev per `steev/CLAUDE.md:14` ("No access to Plan B marketing platform credentials (CMO-only)") |
| `inherit_dirs` | none | No external-dir skill bundles narrowed in |
| `sovereign_only` | `false` | steev intentionally calls Perplexity (hosted) for lightweight WebSearch per `manifest.yaml:90` — disclosed honestly |
| `external_orchestrators` | `[]` | Schema v2 field (DISCLOSURE-SCHEMA §4.6). steev has no exec'd orchestrators (no sandcastle equiv) — empty list. |
## §3 Skills (6)

View File

@ -65,6 +65,224 @@ else
echo " WARN: hermes profile install failed (legacy symlink still works)"
fi
echo ""
# ----------------------------------------------------------------------------
# Wave 7 D6 — disclosure → runtime config wiring (F1-F4)
# Materializes manifest.disclosure (schema v2) into the live Hermes runtime:
# F1 resolve $HERMES_WORKSPACE in inherit_dirs → skills.external_dirs (global)
# F2 compute denylist from disclosure.skills → skills.disabled (per-profile)
# F3 propagate inherit_mcp_toolsets → agent.inherit_mcp_toolsets
# F4 install subrepo pre-push disclosure-drift gate
# Steev = personal scope: profile name is `steev` (no -planb suffix per FRAMEWORK §6.1).
# All steps idempotent + graceful (WARN + skip on missing tooling).
# ----------------------------------------------------------------------------
echo "== disclosure → runtime config (Wave 7 D6) =="
PROFILE_NAME="steev"
HERMES_WORKSPACE="${HERMES_WORKSPACE:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
GLOBAL_CFG="$HERMES_HOME/config.yaml"
PROFILE_CFG="$HOME/.hermes/profiles/$PROFILE_NAME/config.yaml"
# F1 — resolve $HERMES_WORKSPACE in disclosure.inherit_dirs → skills.external_dirs (global config)
if [ "$DRY" = 1 ]; then
echo "DRY: F1 expand inherit_dirs (HERMES_WORKSPACE=$HERMES_WORKSPACE) → $GLOBAL_CFG"
elif command -v yq >/dev/null 2>&1; then
INHERIT_DIRS=$(yq -r '.disclosure.inherit_dirs[]?' "$REPO/manifest.yaml" 2>/dev/null || true)
if [ -n "$INHERIT_DIRS" ]; then
while IFS= read -r raw; do
[ -z "$raw" ] && continue
resolved="${raw//\$HERMES_WORKSPACE/$HERMES_WORKSPACE}"
if [ ! -d "$resolved" ]; then
echo " WARN: F1 inherit_dir not found: $resolved (declared: $raw) — skipping"
continue
fi
python3 - "$GLOBAL_CFG" "$resolved" <<'PY'
import sys, os, yaml
cfg, sk = sys.argv[1], sys.argv[2]
d = yaml.safe_load(open(cfg).read()) if os.path.exists(cfg) else {}
d = d or {}
d.setdefault('skills', {}).setdefault('external_dirs', [])
if sk not in d['skills']['external_dirs']:
d['skills']['external_dirs'].append(sk)
open(cfg, 'w').write(yaml.safe_dump(d, sort_keys=False, allow_unicode=True))
print(f" F1 + {sk}")
else:
print(f" F1 = {sk} (already present)")
PY
done <<< "$INHERIT_DIRS"
else
echo " F1: no disclosure.inherit_dirs declared — skip"
fi
else
echo " WARN: F1 yq not on PATH — skipping inherit_dirs resolution"
fi
# F2 — compute denylist (all builtins NOT in disclosure.skills allowlist) → profile config
if [ "$DRY" = 1 ]; then
echo "DRY: F2 compute builtin denylist → $PROFILE_CFG (skills.disabled)"
elif command -v hermes >/dev/null 2>&1 && command -v yq >/dev/null 2>&1; then
# Try --json first; fall back to table parse w/ box-draw chars (Wave 5 parser).
ALL_BUILTINS=$(hermes skills list --json 2>/dev/null | jq -r '.[] | select(.source=="builtin") | .name' 2>/dev/null || true)
if [ -z "$ALL_BUILTINS" ]; then
ALL_BUILTINS=$(hermes skills list 2>/dev/null | awk -F'│' 'NR>3 && /builtin/ {gsub(/^ +| +$/, "", $2); print $2}' || true)
fi
ALLOWLIST_BUILTIN=$(yq -r '.disclosure.skills[] | select(.source=="builtin") | .id' "$REPO/manifest.yaml" 2>/dev/null | sort -u)
if [ -z "$ALL_BUILTINS" ]; then
echo " WARN: F2 could not enumerate live builtins — skipping denylist"
else
SORTED_BUILTINS=$(echo "$ALL_BUILTINS" | sort -u)
DENYLIST=$(comm -23 <(echo "$SORTED_BUILTINS") <(echo "${ALLOWLIST_BUILTIN:-}") | grep -v '^$' || true)
mkdir -p "$(dirname "$PROFILE_CFG")"
[ -f "$PROFILE_CFG" ] || : > "$PROFILE_CFG"
python3 - "$PROFILE_CFG" <<PY
import sys, yaml
cfg = sys.argv[1]
denylist = [s for s in """$DENYLIST""".splitlines() if s.strip()]
d = yaml.safe_load(open(cfg).read()) or {}
d.setdefault('skills', {})['disabled'] = sorted(set(denylist))
open(cfg, 'w').write(yaml.safe_dump(d, sort_keys=False, allow_unicode=True))
print(f" F2 wrote skills.disabled: {len(denylist)} entr{'y' if len(denylist)==1 else 'ies'}")
PY
fi
else
echo " WARN: F2 hermes/yq missing — skipping denylist"
fi
# F3 — propagate disclosure.inherit_mcp_toolsets to per-profile config.yaml
if [ "$DRY" = 1 ]; then
echo "DRY: F3 write agent.inherit_mcp_toolsets → $PROFILE_CFG"
elif command -v yq >/dev/null 2>&1; then
INHERIT_MCP=$(yq -r '.disclosure.inherit_mcp_toolsets' "$REPO/manifest.yaml" 2>/dev/null || echo "null")
if [ "$INHERIT_MCP" = "null" ] || [ -z "$INHERIT_MCP" ]; then
echo " F3: disclosure.inherit_mcp_toolsets undeclared — skip"
else
mkdir -p "$(dirname "$PROFILE_CFG")"
[ -f "$PROFILE_CFG" ] || : > "$PROFILE_CFG"
python3 - "$PROFILE_CFG" "$INHERIT_MCP" <<'PY'
import sys, yaml
cfg, val = sys.argv[1], sys.argv[2]
b = {"true": True, "false": False}.get(val.lower(), val)
d = yaml.safe_load(open(cfg).read()) or {}
d.setdefault('agent', {})['inherit_mcp_toolsets'] = b
open(cfg, 'w').write(yaml.safe_dump(d, sort_keys=False, allow_unicode=True))
print(f" F3 wrote agent.inherit_mcp_toolsets: {b}")
PY
fi
else
echo " WARN: F3 yq not on PATH — skipping inherit_mcp_toolsets"
fi
# F4 — install subrepo pre-push hook (disclosure-drift gate)
HOOK_DST="$REPO/.git/hooks/pre-push"
if [ "$DRY" = 1 ]; then
echo "DRY: F4 install pre-push hook → $HOOK_DST"
elif [ ! -d "$REPO/.git" ]; then
echo " WARN: F4 $REPO/.git missing — not a git checkout, skip"
else
cat > "$HOOK_DST" <<'HOOK_EOF'
#!/usr/bin/env bash
# pre-push.sh — Wave 7 D6 subrepo disclosure-drift gate
# Schema v2 ref: ../sot/04-STANDARDS/DISCLOSURE-SCHEMA.md
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
PROFILE_DIR_NAME="$(basename "$REPO_ROOT")" # cmo | ceo | cto | steev | curator
PROFILE_NAME="${PROFILE_DIR_NAME}-planb" # default: org-scoped C-suite naming
# Personal-scope profiles drop the -planb suffix (FRAMEWORK §6.1).
[ "$PROFILE_DIR_NAME" = "steev" ] && PROFILE_NAME="steev"
VIOLATIONS=0
emit() { echo "[pre-push:$PROFILE_NAME] $*" >&2; }
fail() { emit "BLOCK: $*"; VIOLATIONS=$((VIOLATIONS + 1)); }
while read -r local_ref local_sha remote_ref remote_sha; do
[ "$local_sha" = "0000000000000000000000000000000000000000" ] && continue
if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then
base=$(git merge-base "$local_sha" jp 2>/dev/null || git rev-parse "$local_sha^" 2>/dev/null || echo "$local_sha")
else
base="$remote_sha"
fi
DELTA=$(git diff --name-only "$base..$local_sha" 2>/dev/null || true)
[ -z "$DELTA" ] && continue
# check 2: manifest governance block
if echo "$DELTA" | grep -q '^manifest\.yaml$'; then
emit "check 2: validating governance block…"
python3 - "$REPO_ROOT/manifest.yaml" <<'PY' || fail "manifest.yaml: governance block invalid"
import sys, yaml
data = yaml.safe_load(open(sys.argv[1]).read()) or {}
gov = data.get("governance")
if not isinstance(gov, dict):
print(f"missing governance: block", file=sys.stderr); sys.exit(1)
req = ["org", "owner", "approval_authority", "vision_refs", "governing_protocols", "standards", "north_star"]
missing = [k for k in req if k not in gov]
if missing:
print(f"governance missing: {', '.join(missing)}", file=sys.stderr); sys.exit(1)
PY
fi
# check 3: identity doc frontmatter
CHANGED_IDENTITY=$(echo "$DELTA" | grep -E '^(AGENT|CONTRACT|DISCLOSURE)\.md$' || true)
if [ -n "$CHANGED_IDENTITY" ]; then
emit "check 3: validating frontmatter…"
for f in $CHANGED_IDENTITY; do
python3 - "$REPO_ROOT/$f" <<'PY' || fail "$f: frontmatter invalid"
import sys, re, yaml
content = open(sys.argv[1]).read()
m = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
if not m: print(f"missing YAML frontmatter", file=sys.stderr); sys.exit(1)
fm = yaml.safe_load(m.group(1)) or {}
req = {"name", "tier", "status", "owner", "source", "last_reviewed", "description"}
missing = req - set(fm.keys())
if missing: print(f"frontmatter missing: {', '.join(missing)}", file=sys.stderr); sys.exit(1)
PY
done
fi
# check 6: disclosure drift (only if manifest changed)
if echo "$DELTA" | grep -q '^manifest\.yaml$'; then
emit "check 6: disclosure drift…"
# 6.a skills drift
if command -v hermes >/dev/null 2>&1; then
declared=$(yq -r '.disclosure.skills[].id' "$REPO_ROOT/manifest.yaml" 2>/dev/null | sort -u)
live=$(hermes -p "$PROFILE_NAME" skills list 2>/dev/null | awk 'NR>3 && /enabled|│ *enabled/ {for (i=1; i<=NF; i++) if ($i != "│" && $i != "enabled") {print $i; break}}' | sort -u || echo "")
if [ -n "$live" ]; then
drift=$(diff <(echo "$declared") <(echo "$live") 2>/dev/null || true)
[ -n "$drift" ] && fail "skills drift: $drift"
else
emit " (skip 6.a — hermes CLI live-skill query returned empty; WARN)"
fi
else
emit " (skip 6.a — hermes CLI not on PATH; WARN)"
fi
# 6.b/6.c/6.d/6.e simplified: just confirm manifest yq parses + sovereign_only invariant
if [ "$(yq '.disclosure.sovereign_only' "$REPO_ROOT/manifest.yaml")" = "true" ]; then
bad=$(yq -r '.disclosure.skills[] | select(.hosted_api != null) | .id' "$REPO_ROOT/manifest.yaml" 2>/dev/null)
[ -n "$bad" ] && fail "sovereign_only=true but skills declare hosted_api: $bad"
fi
fi
# bypass marker categorization
for sha in $(git rev-list "$base..$local_sha"); do
MSG=$(git log -1 --format=%B "$sha")
bypass_line=$(echo "$MSG" | grep -E '^enforcement-bypass:' || true)
if [ -n "$bypass_line" ]; then
if ! echo "$bypass_line" | grep -qE '^enforcement-bypass: (emergency|upstream-blocker|schema-migration|hermes-bug|third-party-bug) — '; then
fail "$sha: bypass marker uncategorized — required: 'enforcement-bypass: <CATEGORY> — <line>'"
fi
fi
done
done
if [ "$VIOLATIONS" -gt 0 ]; then
emit "$VIOLATIONS violation(s) — push blocked"
exit 1
fi
emit "✓ subrepo pre-push gate passed"
exit 0
HOOK_EOF
chmod +x "$HOOK_DST"
echo " F4 installed: $HOOK_DST"
fi
echo ""
echo "== done =="
echo " verify skills: hermes -p steev skills list | grep steev-agent"

View File

@ -99,13 +99,14 @@ sovereignty:
# Pre-push hook check 6 enforces this == live `hermes -p steev …` runtime.
disclosure:
scope: personal
schema_version: 1
schema_version: 2
chat_facing: true # sole JP chat touchpoint per CLAUDE.md L7-L8
delegates_to: [ceo-planb] # business work routed to CEO via kanban
inherit_builtins: false # deny Hermes 84-builtin default; allowlist below
inherit_mcp_toolsets: false # deny host MCP propagation (closes bte leak)
sovereign_only: false # perplexity (hosted) intentionally called for WebSearch
inherit_dirs: []
external_orchestrators: [] # steev has no exec'd orchestrators (no sandcastle equiv)
skills:
- id: steev-agent