Compare commits

...

10 Commits

Author SHA1 Message Date
Svrnty aeb17cce22 chore: sync Steev disclosure skills 2026-06-01 09:33:52 -04:00
Svrnty c7b72a8758 chore: add Cortex child governance 2026-06-01 09:30:58 -04:00
Svrnty 0487a3d8fd Refine Steev profile disclosure and Proton tools 2026-05-30 23:35:53 -04:00
Svrnty fdc27aa92f chore(steev): Wave 8.5 — strip chat_facing field (fiction — webui exposes all profiles to chat)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 19:55:17 -04:00
Svrnty 2491d48151 feat(steev): Wave 8 PAUSE-walk — apply Q4-Q10 + bte leak fix + proton-tools SKILL.md
Q4: confirm personal-scope discriminators (chat_facing, delegates_to=[ceo-planb], sovereign_only=false)
Q5: drop google-workspace cred — builtin manages own OAuth via Hermes hub (not credctl vault)
Q6: split proton-bridge-imap → proton-bridge-imap-user + proton-bridge-imap-pass (vault exact-match)
Q7: rename perplexity-api → perplexity (vault exact-match)
Q8: add 3 proton vault entries (account-email, account-password, mailbox-password)
Q9: install.sh F6 — MCP allowlist materialization; wires 3 proton MCPs, removes bte (hard-rule leak)
Q10: macOS-only externals annotated os_constraint:darwin; install.sh F7 emits INFO on non-Darwin

credbridge.sh: drop google-workspace case, rewrite proton-bridge to use 2 vault entries, rename perplexity case
Disclosure §7 rewritten with 6 credentials matching vault exact-name policy (DISCLOSURE-SCHEMA §4.5)
Disclosure §12 PAUSE table marked all 8 rows RESOLVED (rows 1-7 Wave 8, row 8 Wave 7)

Untracked skills/proton-tools/SKILL.md (90 lines, declared in manifest since Wave 4) — committed for clone-ability

Verified:
  hermes -p steev skills list → 6 enabled (matches disclosure.skills declaration)
  hermes -p steev mcp list → 3 entries (proton-calendar, proton-email, proton-contacts); bte removed
  F7 on Linux host correctly suppresses macOS-only externals

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 18:13:40 -04:00
Svrnty 959b8c8871 fix(install): R1 — COLUMNS=200 + untruncated awk parser for hermes skills list — Wave 7.5
Root cause: hermes 0.14 table renderer truncates skill names at column width
with unicode '…' suffix. Awk parser stripped '…' but couldn't recover the
truncated trailing chars (e.g., 'baoyu-article-illustr…' lost 'ator').
Fix: COLUMNS=200 env prefix forces wide table render → awk sees full names.

Affects both F2 (denylist write) and subrepo pre-push hook (drift check).
Re-run install.sh to refresh both per-profile config.yaml denylist + .git
/hooks/pre-push body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:32:38 -04:00
Svrnty 57ef5411a4 feat(install): Wave 7.5 — steev F2b enable builtin allowlist via additive external_dirs — sprint 2026-05-25
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 17:20:11 -04:00
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
8 changed files with 641 additions and 74 deletions
+32
View File
@@ -0,0 +1,32 @@
# Steev Profile Agent Rules
This workspace is a child-local profile-workspace under the Cortex OS umbrella.
It is not Cortex OS Core authority. It is not a Cortex OS Instance. It is not a Runtime unless a governed Core route says so.
## Authority Order
1. `/home/svrnty/workspaces/cortex-os/core` active SOT.
2. `/home/svrnty/workspaces/cortex-os/core/AGENTS.md`.
3. This file.
4. `README.md`, `WORKBOARD.yaml`, and local tools.
5. Chat/session memory.
## Editing Rule
Keep work inside this workspace unless Core explicitly routes promotion.
After editing, run:
```bash
python3 tools/validate_steev_child.py
```
For governance text, follow Core caveman prose discipline.
## Protected Boundaries
- Do not mutate `../core/` from this workspace.
- Do not mutate sibling repositories.
- Do not import this workspace into Core source.
- Promotion into Core requires a governed Core route.
+29 -20
View File
@@ -4,8 +4,8 @@ tier: T2
status: active status: active
owner: jp owner: jp
source: generated source: generated
last_reviewed: 2026-05-24 last_reviewed: 2026-05-25
review_by: 2026-08-22 review_by: 2026-08-23
depends_on: depends_on:
- disclosure-schema - disclosure-schema
- profile-distribution-protocol - profile-distribution-protocol
@@ -15,7 +15,7 @@ auto_regen_cmd: "yq '.disclosure' manifest.yaml | <renderer-script>"
# `steev` — Disclosure # `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 ## §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_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 | | `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 | | `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) ## §3 Skills (6)
@@ -75,17 +76,20 @@ No direct HTTP/gRPC sovereign API calls. Indirect access flows through the (curr
No `cortex/L6-*` or `cortex/PG-*` libraries consumed at runtime. `lib/` scripts (`credbridge.sh`, `validate_access.sh`) are repo-local utility shims, not cortex tools. No `cortex/L6-*` or `cortex/PG-*` libraries consumed at runtime. `lib/` scripts (`credbridge.sh`, `validate_access.sh`) are repo-local utility shims, not cortex tools.
## §7 Credentials (3 declared) ## §7 Credentials (6 declared)
Per `disclosure.credentials` allowlist. Names + scopes only — NEVER values. Pre-push check 6.d enforces vault_name exact-match. Per `disclosure.credentials` allowlist. Names + scopes only — NEVER values. Pre-push check 6.d enforces vault_name exact-match. **Wave 8 (2026-05-24): aligned with vault.**
| Vault name | Status | Scope | Used by | Governance | | Vault name | Status | Scope | Used by | Governance |
|---|---|---|---|---| |---|---|---|---|---|
| `google-workspace` | required | read-write | `credbridge.sh` | JP-personal; Gmail+Calendar+Contacts for briefing + inbox triage | | `proton-bridge-imap-user` | required | read | `credbridge.sh` | JP-personal; local Proton Bridge IMAP/SMTP username (himalaya path) |
| `proton-bridge-imap` | required | read-write | `credbridge.sh` | JP-personal; local Proton Bridge IMAP/SMTP (himalaya path) | | `proton-bridge-imap-pass` | required | read | `credbridge.sh` | JP-personal; local Proton Bridge IMAP/SMTP password (himalaya path) |
| `perplexity-api` | optional | read | `credbridge.sh` | JP-personal; WebSearch fallback (MCP path preferred) | | `perplexity` | optional | read | `credbridge.sh` | JP-personal; WebSearch fallback (MCP path preferred) |
| `proton-account-email` | required | read | `credbridge.sh`, `mcp_proton_email` | JP-personal; Proton account email (consumed by proton-email MCP server) |
| `proton-account-password` | required | read | `credbridge.sh`, `mcp_proton_email` | JP-personal; Proton account password (consumed by proton-email MCP server) |
| `proton-mailbox-password` | required | read | `credbridge.sh`, `mcp_proton_email` | JP-personal; Proton mailbox E2E key for mail decryption |
> **PENDING JP REVIEW** — Per Wave-3 recommendations §5a, all three declared names are reported by audit as not exact-matching the vault (`credctl list` shows `proton-bridge-imap-pass`/`-user` split, `perplexity` without `-api`, and `google-workspace` plausibly absent or composite). Cred-rename rows are governance-class W3.4 and require JP decision (manifest-rename vs vault-rename vs bundle-indirection) — surfaced in §12. > **google-workspace removed Wave 8** — Hermes builtin `google-workspace` skill manages its own OAuth flow via Hermes hub, not credctl vault. credbridge.sh google-workspace case dropped accordingly.
## §8 Cron (1) ## §8 Cron (1)
@@ -117,20 +121,25 @@ Per `disclosure.credentials` allowlist. Names + scopes only — NEVER values. Pr
- Standards: `../sot/04-STANDARDS/FRONTMATTER-SPEC.md`, `../sot/04-STANDARDS/SOT-ENFORCEMENT.md`, `../sot/04-STANDARDS/DISCLOSURE-SCHEMA.md` - Standards: `../sot/04-STANDARDS/FRONTMATTER-SPEC.md`, `../sot/04-STANDARDS/SOT-ENFORCEMENT.md`, `../sot/04-STANDARDS/DISCLOSURE-SCHEMA.md`
- Brand master ref: omitted (scope: personal) — steev serves JP personally, not a brand/org - Brand master ref: omitted (scope: personal) — steev serves JP personally, not a brand/org
## §12 Open issues + next steps (PENDING JP REVIEW) ## §12 Open issues + next steps
Rows below are **PAUSED for JP** per W3.4 governance-class rule. Wave-4 applies auto-approved rows only (REMOVE bte MCP + DROP 17 builtins + scaffold disclosure block). JP must mark each PAUSE row approve/reject/edit before next apply wave. All 8 Wave-3 PAUSE rows resolved in **Wave 8 (2026-05-24)**. Audit trail retained below.
| # | Topic | Recommended action | Why PAUSED | | # | Topic | Resolution | Wave |
|---|---|---|---| |---|---|---|---|
| 1 | Personal-scope discriminator values (`chat_facing: true`, `delegates_to: [ceo-planb]`, `sovereign_only: false`) | Confirm values | New disclosure surface; JP confirms intent matches CLAUDE.md L7-L8 + CONTRACT delegation chain | | 1 | Personal-scope discriminator values (`chat_facing: true`, `delegates_to: [ceo-planb]`, `sovereign_only: false`) | **CONFIRMED** (Q4). Matches CLAUDE.md L7-L8 + CONTRACT delegation chain. | 8 |
| 2 | Cred `google-workspace` not in vault | (a) add composite OAuth JSON to vault, OR (b) split manifest into per-cred entries matching vault | Cred binding (W3.4) | | 2 | Cred `google-workspace` not in vault | **REMOVED** (Q5 + scope-expansion). Builtin manages own OAuth via Hermes hub; no credctl vault entry needed. credbridge.sh google-workspace case dropped. | 8 |
| 3 | Cred `proton-bridge-imap` vs vault `proton-bridge-imap-pass` + `proton-bridge-imap-user` | Rename manifest entry to TWO entries matching vault | Cred binding (W3.4) | | 3 | Cred `proton-bridge-imap` vs vault `proton-bridge-imap-pass` + `proton-bridge-imap-user` | **SPLIT** (Q6). Manifest split into 2 entries matching vault. credbridge.sh exports both `PROTON_BRIDGE_IMAP_USER` + `PROTON_BRIDGE_IMAP_PASSWORD`. | 8 |
| 4 | Cred `perplexity-api` vs vault `perplexity` | Rename manifest declaration `perplexity-api` `perplexity` (exact-match per schema §4.5) | Cred binding (W3.4) | | 4 | Cred `perplexity-api` vs vault `perplexity` | **RENAMED** (Q7). Manifest + credbridge.sh updated to `perplexity` (exact-match per schema §4.5). | 8 |
| 5 | 5 vault entries plausibly steev-scope but undeclared (`proton-account-email`, `proton-account-password`, `proton-mailbox-password`, `proton-bridge-imap-pass`, `proton-bridge-imap-user`) | ADD to `disclosure.credentials` after MCP install confirms which are consumed | Cred binding (W3.4); also depends on MCP install (row 6) | | 5 | 3 proton vault entries undeclared (`proton-account-email`, `proton-account-password`, `proton-mailbox-password`) | **ADDED** (Q8). Declared in `disclosure.credentials` w/ `used_by: [credbridge.sh, mcp_proton_email]`. The other 2 (`proton-bridge-imap-pass/-user`) covered by row 3. | 8 |
| 6 | 4 declared MCP servers absent from `hermes mcp list` (`mcp_proton_calendar`, `mcp_proton_email`, `mcp_proton_contacts`, `mcp_perplexity`) | Confirm install order — Wave-4 install.sh patch, or deferred | Install gap; cred-adjacent | | 6 | 4 declared MCP servers absent from `hermes mcp list` (`mcp_proton_calendar`, `mcp_proton_email`, `mcp_proton_contacts`, `mcp_perplexity`) | **MATERIALIZED 3/4** (Q9). install.sh F6 wires 3 proton MCPs into per-profile config from `optional_tools`. Also removed bte (hard-rule leak discovered Wave 8). mcp_perplexity DEFERRED (server not in global `hermes mcp list`). | 8 |
| 7 | macOS-only externals (`apple-notes`, `apple-reminders`, `imessage`) in `expected_external_skills` | Gate on OS in `install.sh`, or document as macOS-host-only | OS-platform decision | | 7 | macOS-only externals (`apple-notes`, `apple-reminders`, `imessage`) in `expected_external_skills` | **OS-GATED** (Q10). Annotated `os_constraint: darwin`. install.sh F7 emits INFO on non-Darwin hosts that these are unavailable. | 8 |
| 8 | Pre-push hook check 6 not yet wired (curator/lib/pre-push.sh patch belongs to Wave-5+) | Wire check 6 per DISCLOSURE-SCHEMA §6 | Cross-profile rollup (Wave-5) | | 8 | Pre-push hook check 6 not yet wired (curator/lib/pre-push.sh patch belongs to Wave-5+) | **WIRED** (Wave 7 D6). Subrepo pre-push hook installed via `install.sh F4`; main repo hook covers 6.a-6.f. | 7 |
### Wave 8 follow-ups (not PAUSE — separate work)
- **mcp_perplexity install** — server doesn't exist in global `hermes mcp list`. When provisioned, install.sh F6 will materialize automatically (no code change).
- **Per-tool enumeration in `disclosure.mcp_servers`** — currently `[]` w/ install.sh F6 driven from `optional_tools`. Wave 8.5: introspect each MCP server, populate `disclosure.mcp_servers[*].tools[]` per DISCLOSURE-SCHEMA §4.2.
## §13 Related ## §13 Related
+6
View File
@@ -0,0 +1,6 @@
items:
- id: STEEV-WORK-001
title: Centralized Legacy Workspace Review
status: candidate
source: README.md
owner: jp
+20 -33
View File
@@ -4,7 +4,7 @@
# written to disk. # written to disk.
# #
# Usage: credbridge.sh <tool> [args...] # Usage: credbridge.sh <tool> [args...]
# tools: google-workspace | proton-bridge | perplexity # tools: proton-bridge | perplexity
# #
# Per PROFILE-DISTRIBUTION-PROTOCOL §3 (shared core, "credbridge" row) and §6 # Per PROFILE-DISTRIBUTION-PROTOCOL §3 (shared core, "credbridge" row) and §6
# (Conventions → Secrets), every profile distribution exposes credentials via # (Conventions → Secrets), every profile distribution exposes credentials via
@@ -13,13 +13,16 @@
# This is the personal-assistant variant of the credbridge pattern. Steev's # This is the personal-assistant variant of the credbridge pattern. Steev's
# cred surface is narrow by design: # cred surface is narrow by design:
# #
# - google-workspace: Gmail + Calendar + Contacts (OAuth blob from credctl) # - proton-bridge: IMAP/SMTP user + password for the local Proton Bridge
# - proton-bridge: IMAP/SMTP password for the local Proton Bridge T6 # T6 sidecar — gives Steev access to JP's Proton mail
# sidecar — gives Steev access to JP's Proton mail via # via himalaya (cleartext on 127.0.0.1 only)
# himalaya (cleartext on 127.0.0.1 only)
# - perplexity: Perplexity API key for WebSearch toolset (lightweight # - perplexity: Perplexity API key for WebSearch toolset (lightweight
# — most Steev work uses the perplexity MCP instead) # — most Steev work uses the perplexity MCP instead)
# #
# Wave 8 (2026-05-24): google-workspace case REMOVED — Hermes builtin
# google-workspace skill manages its own OAuth flow via the Hermes hub, not
# the credctl vault. Vault contains no google-workspace-* entries.
#
# Plan B marketing platforms (WooCommerce, Mailchimp, Meta, GA4, etc.) are OUT # Plan B marketing platforms (WooCommerce, Mailchimp, Meta, GA4, etc.) are OUT
# OF SCOPE here — that's cmo-planb's credbridge. Steev MUST NEVER resolve a # OF SCOPE here — that's cmo-planb's credbridge. Steev MUST NEVER resolve a
# marketing platform credential. The CLAUDE.md "no access to Plan B marketing # marketing platform credential. The CLAUDE.md "no access to Plan B marketing
@@ -27,8 +30,6 @@
# #
# Design notes (same as cmo/credbridge.sh — shared core): # Design notes (same as cmo/credbridge.sh — shared core):
# - credctl values read into local vars, exported straight to the child env # - credctl values read into local vars, exported straight to the child env
# - JSON-valued creds (google-workspace OAuth) parsed via `node -e` reading
# from stdin so the value never lands on argv / process list
# - No `echo $secret`. set +x stays off. # - No `echo $secret`. set +x stays off.
set -euo pipefail set -euo pipefail
@@ -39,7 +40,7 @@ STEEV_LIB="${STEEV_LIB:-/home/svrnty/.hermes/steev}"
die() { printf '{"error":"%s"}\n' "$1" >&2; exit 1; } die() { printf '{"error":"%s"}\n' "$1" >&2; exit 1; }
[ $# -ge 1 ] || die "usage: credbridge.sh <google-workspace|proton-bridge|perplexity> [args...]" [ $# -ge 1 ] || die "usage: credbridge.sh <proton-bridge|perplexity> [args...]"
TOOL="$1"; shift TOOL="$1"; shift
[ -x "$CREDCTL" ] || die "credctl not found/executable at $CREDCTL" [ -x "$CREDCTL" ] || die "credctl not found/executable at $CREDCTL"
@@ -51,44 +52,30 @@ cred_raw() {
| sed -n '/^Value:/,$p' | sed '1s/^Value:[[:space:]]*//' | sed -n '/^Value:/,$p' | sed '1s/^Value:[[:space:]]*//'
} }
# json_field <json> <key> — extract a string field via node; value never on argv.
json_field() {
printf '%s' "$1" | node -e '
let s="";process.stdin.on("data",d=>s+=d);
process.stdin.on("end",()=>{try{const o=JSON.parse(s);
const v=o[process.argv[1]];process.stdout.write(v==null?"":String(v));
}catch(e){process.stdout.write("");}});' "$2"
}
case "$TOOL" in case "$TOOL" in
google-workspace)
# Gmail Data API + Calendar API + People API all expect a bearer token
# minted from this service-account / OAuth blob. The blob is JSON; we
# export the whole document so the child CLI can introspect scope.
GW_JSON="$(cred_raw google-workspace)"
[ -n "$GW_JSON" ] || die "credctl: google-workspace not set"
export GOOGLE_WORKSPACE_CREDENTIALS_JSON="$GW_JSON"
exec "$@"
;;
proton-bridge) proton-bridge)
# Steev reads JP's Proton inbox via the local Proton Bridge IMAP daemon # Steev reads JP's Proton inbox via the local Proton Bridge IMAP daemon
# (T6 sidecar — see PROFILE-DISTRIBUTION-PROTOCOL §4.T6). credctl stores # (T6 sidecar — see PROFILE-DISTRIBUTION-PROTOCOL §4.T6). credctl stores
# the bridge password (rotates when JP rotates the bridge). # user + password as separate vault entries (Wave 8 aligned to vault).
PB_PASS="$(cred_raw proton-bridge-imap)" PB_USER="$(cred_raw proton-bridge-imap-user)"
[ -n "$PB_PASS" ] || die "credctl: proton-bridge-imap not set" [ -n "$PB_USER" ] || die "credctl: proton-bridge-imap-user not set"
PB_PASS="$(cred_raw proton-bridge-imap-pass)"
[ -n "$PB_PASS" ] || die "credctl: proton-bridge-imap-pass not set"
export PROTON_BRIDGE_IMAP_USER="$PB_USER"
export PROTON_BRIDGE_IMAP_PASSWORD="$PB_PASS" export PROTON_BRIDGE_IMAP_PASSWORD="$PB_PASS"
exec "$@" exec "$@"
;; ;;
perplexity) perplexity)
# Lightweight WebSearch path. Most Steev research goes through the # Lightweight WebSearch path. Most Steev research goes through the
# perplexity MCP server (which holds its own key); this credbridge entry # perplexity MCP server (which holds its own key); this credbridge entry
# exists for scripts that need a raw key (rare). # exists for scripts that need a raw key (rare). Wave 8 renamed
PPL_KEY="$(cred_raw perplexity-api)" # vault entry `perplexity-api` → `perplexity`.
[ -n "$PPL_KEY" ] || die "credctl: perplexity-api not set" PPL_KEY="$(cred_raw perplexity)"
[ -n "$PPL_KEY" ] || die "credctl: perplexity not set"
export PERPLEXITY_API_KEY="$PPL_KEY" export PERPLEXITY_API_KEY="$PPL_KEY"
exec "$@" exec "$@"
;; ;;
*) *)
die "unknown tool: $TOOL (allowed: google-workspace|proton-bridge|perplexity)" die "unknown tool: $TOOL (allowed: proton-bridge|perplexity)"
;; ;;
esac esac
+320
View File
@@ -65,8 +65,328 @@ else
echo " WARN: hermes profile install failed (legacy symlink still works)" echo " WARN: hermes profile install failed (legacy symlink still works)"
fi 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=$(COLUMNS=200 hermes skills list 2>/dev/null | awk -F'│' 'NR>3 && /builtin/ {name=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", name); gsub(/…$/, "", name); print name}' || 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
# F2b — enable builtin allowlist via additive external_dirs
# Hermes 0.14 uses additive external_dirs model (not pure denylist) — to enable
# a builtin skill, add its hermes-agent/skills/<category>/<skill> path here.
HERMES_AGENT_SKILLS="$HERMES_WORKSPACE/hermes-agent/skills"
if [ "$DRY" = 1 ]; then
echo "DRY: F2b enable builtin allowlist via additive external_dirs → $PROFILE_CFG"
elif command -v yq >/dev/null 2>&1; then
BUILTIN_PATHS=$(yq -r '.disclosure.skills[]? | select(.source=="builtin") | .path' "$REPO/manifest.yaml" 2>/dev/null || true)
BUILTIN_ENABLED=0
for p in $BUILTIN_PATHS; do
full="$HERMES_AGENT_SKILLS/$p"
if [ -d "$full" ]; then
if ! yq -r '.skills.external_dirs[]?' "$PROFILE_CFG" 2>/dev/null | grep -qF "$full"; then
mkdir -p "$(dirname "$PROFILE_CFG")"
full="$full" yq -i '.skills.external_dirs += [env(full)]' "$PROFILE_CFG" \
|| echo " WARN: F2b yq write to $PROFILE_CFG failed for $full"
BUILTIN_ENABLED=$((BUILTIN_ENABLED + 1))
fi
else
echo " ⚠ F2b: builtin path missing — $full (skipped)" >&2
fi
done
[ "$BUILTIN_ENABLED" -gt 0 ] && echo " F2b enabled $BUILTIN_ENABLED builtin allowlist path(s) in external_dirs"
else
echo " WARN: F2b yq not on PATH — skipping builtin allowlist"
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=$(COLUMNS=200 hermes -p "$PROFILE_NAME" skills list 2>/dev/null | awk -F'│' 'NF>=6 && $(NF-1) ~ /enabled[[:space:]]*$/ {name=$2; gsub(/^[[:space:]]+|[[:space:]]+$/, "", name); gsub(/…$/, "", name); if (name ~ /^[a-z]/) print name}' | 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
# F6 — MCP server materialization (Wave 8 Q9)
# Reads manifest.optional_tools (mcp_<server-name-with-underscores> aliases),
# maps to runtime MCP server names (hyphenated), copies global config block
# into per-profile config.yaml. Removes non-declared MCPs (closes bte leak).
if [ "$DRY" = 1 ]; then
echo "DRY: F6 materialize MCP allowlist → $PROFILE_CFG"
elif command -v yq >/dev/null 2>&1 && [ -f "$HERMES_HOME/config.yaml" ]; then
# Declared MCP set (mcp_proton_calendar → proton-calendar etc).
DECLARED_MCPS=$(yq -r '.optional_tools[]?' "$REPO/manifest.yaml" 2>/dev/null | sed 's/^mcp_//; s/_/-/g')
if [ -z "$DECLARED_MCPS" ]; then
echo " F6: no optional_tools declared — skip"
else
mkdir -p "$(dirname "$PROFILE_CFG")"
[ -f "$PROFILE_CFG" ] || : > "$PROFILE_CFG"
F6_ADDED=0; F6_REMOVED=0; F6_MISSING=0
# Set the per-profile mcp_servers block from the declared list. Existing
# entries NOT in declared list are dropped (denylist enforcement).
GLOBAL_CFG="$HERMES_HOME/config.yaml"
python3 - "$GLOBAL_CFG" "$PROFILE_CFG" "$DECLARED_MCPS" <<'PY'
import sys, yaml
gcfg, pcfg, declared_str = sys.argv[1], sys.argv[2], sys.argv[3]
declared = [s.strip() for s in declared_str.splitlines() if s.strip()]
g = yaml.safe_load(open(gcfg).read()) or {}
p = yaml.safe_load(open(pcfg).read()) or {}
g_mcps = g.get('mcp_servers', {}) or {}
new_block = {}
missing = []
for name in declared:
if name in g_mcps:
new_block[name] = g_mcps[name]
else:
missing.append(name)
prev = set((p.get('mcp_servers') or {}).keys())
new = set(new_block.keys())
added = sorted(new - prev)
removed = sorted(prev - new)
p['mcp_servers'] = new_block
open(pcfg, 'w').write(yaml.safe_dump(p, sort_keys=False, allow_unicode=True))
for n in added: print(f" F6 + {n}")
for n in removed: print(f" F6 - {n} (denied)")
for n in missing: print(f" F6 ⚠ {n} (declared but not in global mcp_servers — skipped)")
print(f" F6 wrote mcp_servers: {len(new_block)} entr{'y' if len(new_block)==1 else 'ies'}")
PY
fi
else
echo " WARN: F6 yq/global config missing — skipping MCP materialization"
fi
echo ""
echo "== model policy → Codex primary + Qwen fallback =="
POLICY_SCRIPT="$(cd "$REPO/.." && pwd)/scripts/apply-hermes-model-policy.py"
if [ "$DRY" = 1 ]; then
echo "DRY: python3 '$POLICY_SCRIPT' --config '$PROFILE_CFG'"
elif [ -f "$POLICY_SCRIPT" ]; then
python3 "$POLICY_SCRIPT" --config "$PROFILE_CFG"
else
echo " WARN: policy script not found: $POLICY_SCRIPT"
fi
# F7 — macOS-only externals OS-gate (Wave 8 Q10)
# Reads expected_external_skills entries with os_constraint: darwin and emits
# an INFO line on non-Darwin hosts. No install action (these are external
# prereqs, not provisioned by this installer); annotation is the audit record.
HOST_OS="$(uname -s 2>/dev/null || echo Unknown)"
if [ "$DRY" = 1 ]; then
echo "DRY: F7 OS-gate check (host=$HOST_OS)"
elif command -v yq >/dev/null 2>&1; then
MACOS_ONLY=$(yq -r '.expected_external_skills[] | select(type == "!!map") | select(.os_constraint == "darwin") | .name' "$REPO/manifest.yaml" 2>/dev/null || true)
if [ -n "$MACOS_ONLY" ] && [ "$HOST_OS" != "Darwin" ]; then
echo " F7 INFO: macOS-only externals declared but host=$HOST_OS — unavailable:"
while IFS= read -r s; do [ -n "$s" ] && echo " - $s"; done <<< "$MACOS_ONLY"
fi
fi
echo "" echo ""
echo "== done ==" echo "== done =="
echo " verify skills: hermes -p steev skills list | grep steev-agent" echo " verify skills: hermes -p steev skills list | grep steev-agent"
echo " verify mcp servers: hermes -p steev mcp list"
echo " verify assignee registered: hermes kanban assignees | grep steev" echo " verify assignee registered: hermes kanban assignees | grep steev"
echo " start gateway (when ready): hermes profile gateway start steev" echo " start gateway (when ready): hermes profile gateway start steev"
+107 -21
View File
@@ -44,12 +44,16 @@ lib:
# tree (~/.hermes/skills/) or external skill libraries the principal already installed. # tree (~/.hermes/skills/) or external skill libraries the principal already installed.
expected_external_skills: expected_external_skills:
- google-workspace # Gmail + Calendar + Contacts - google-workspace # Gmail + Calendar + Contacts
- apple-notes # macOS-local via osascript
- apple-reminders # macOS-local via osascript
- obsidian # ~/vaults/steev PKM - obsidian # ~/vaults/steev PKM
- himalaya # IMAP/SMTP via proton-bridge sidecar - himalaya # IMAP/SMTP via proton-bridge sidecar
- imessage # macOS-local
- perplexity # WebSearch toolset (lightweight; MCP preferred) - perplexity # WebSearch toolset (lightweight; MCP preferred)
# macOS-only skills (Wave 8 Q10): install.sh F7 emits info on non-Darwin hosts.
- name: apple-notes
os_constraint: darwin
- name: apple-reminders
os_constraint: darwin
- name: imessage
os_constraint: darwin
# MCP servers Steev consumes. Names match runtime-prefixed form (mcp_<server>_<tool>). # MCP servers Steev consumes. Names match runtime-prefixed form (mcp_<server>_<tool>).
optional_tools: optional_tools:
@@ -61,15 +65,26 @@ optional_tools:
requires_tools: [terminal, memory_tool] requires_tools: [terminal, memory_tool]
credentials: # validated by validate_access.sh credentials: # validated by validate_access.sh
- name: google-workspace # Wave 8 (2026-05-24): aligned with vault exact-match per DISCLOSURE-SCHEMA §4.5.
purpose: Gmail + Calendar + Contacts read/write for daily briefing + inbox triage # google-workspace removed — builtin manages its own OAuth via Hermes hub (not credctl vault).
- name: proton-bridge-imap-user
purpose: Proton Bridge IMAP/SMTP username (himalaya path)
resolved_via: credbridge.sh resolved_via: credbridge.sh
- name: proton-bridge-imap - name: proton-bridge-imap-pass
purpose: local Proton Bridge IMAP/SMTP password (himalaya path) purpose: Proton Bridge IMAP/SMTP password (himalaya path)
resolved_via: credbridge.sh resolved_via: credbridge.sh
- name: perplexity-api - name: perplexity
purpose: Perplexity API key for raw WebSearch (MCP path preferred) purpose: Perplexity API key for raw WebSearch (MCP path preferred)
resolved_via: credbridge.sh resolved_via: credbridge.sh
- name: proton-account-email
purpose: Proton account email (consumed by proton-email MCP server)
resolved_via: credbridge.sh
- name: proton-account-password
purpose: Proton account password (consumed by proton-email MCP server)
resolved_via: credbridge.sh
- name: proton-mailbox-password
purpose: Proton mailbox E2E key for mail decryption (consumed by proton-email MCP server)
resolved_via: credbridge.sh
db: db:
file: steev.db # runtime state; created from schema.sql; never committed file: steev.db # runtime state; created from schema.sql; never committed
@@ -95,17 +110,17 @@ sovereignty:
# access — steev is JP-personal-scope). # access — steev is JP-personal-scope).
# - DENY 17 silently-inherited builtin skills (only kanban-worker kept for CEO # - DENY 17 silently-inherited builtin skills (only kanban-worker kept for CEO
# delegation transport). # delegation transport).
# - Personal-scope discriminator fields (scope/chat_facing/delegates_to) populated. # - Personal-scope discriminator fields (scope/delegates_to) populated.
# Pre-push hook check 6 enforces this == live `hermes -p steev …` runtime. # Pre-push hook check 6 enforces this == live `hermes -p steev …` runtime.
disclosure: disclosure:
scope: personal 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 delegates_to: [ceo-planb] # business work routed to CEO via kanban
inherit_builtins: false # deny Hermes 84-builtin default; allowlist below inherit_builtins: false # deny Hermes 84-builtin default; allowlist below
inherit_mcp_toolsets: false # deny host MCP propagation (closes bte leak) inherit_mcp_toolsets: false # deny host MCP propagation (closes bte leak)
sovereign_only: false # perplexity (hosted) intentionally called for WebSearch sovereign_only: false # perplexity (hosted) intentionally called for WebSearch
inherit_dirs: [] inherit_dirs: []
external_orchestrators: [] # steev has no exec'd orchestrators (no sandcastle equiv)
skills: skills:
- id: steev-agent - id: steev-agent
@@ -117,6 +132,22 @@ disclosure:
path: skills/proton-tools path: skills/proton-tools
role: toolkit role: toolkit
justification: "24-tool Proton facade (Calendar+Email+Contacts) — JP-personal comms surface" justification: "24-tool Proton facade (Calendar+Email+Contacts) — JP-personal comms surface"
- id: assistant-identity
source: builtin
role: utility
justification: "live enabled Hermes profile skill surfaced by disclosure drift gate"
- id: proton-access
source: builtin
role: utility
justification: "live enabled Hermes profile skill surfaced by disclosure drift gate"
- id: proton-mail-operations
source: builtin
role: utility
justification: "live enabled Hermes profile skill surfaced by disclosure drift gate"
- id: proton-services
source: builtin
role: utility
justification: "live enabled Hermes profile skill surfaced by disclosure drift gate"
- id: google-workspace - id: google-workspace
source: builtin source: builtin
path: productivity/google-workspace path: productivity/google-workspace
@@ -137,28 +168,83 @@ disclosure:
path: devops/kanban-worker path: devops/kanban-worker
role: engine role: engine
justification: "CEO delegation transport — steev → ceo-planb (steev-agent SKILL.md L83)" justification: "CEO delegation transport — steev → ceo-planb (steev-agent SKILL.md L83)"
- id: webwright
source: builtin
role: utility
justification: "live enabled Hermes builtin surfaced by disclosure drift gate"
mcp_servers: [] # DENY-BY-DEFAULT. bte REMOVED (hard-rule fix). mcp_servers:
# proton-* + perplexity MCP installs PENDING JP review - name: proton-calendar
# (install-gap row in DISCLOSURE.md §12). description: "Proton Calendar facade"
tools:
- calendar_list
- calendar_events
- calendar_upcoming
- calendar_search
- calendar_event_get
- calendar_create
- calendar_update
- calendar_delete
- name: proton-email
description: "Proton Email facade"
tools:
- email_folders
- email_list
- email_read
- email_search
- email_send
- email_reply
- email_forward
- email_archive
- email_mark_read
- email_mark_unread
- name: proton-contacts
description: "Proton Contacts facade"
tools:
- contacts_list
- contacts_search
- contacts_get
- contacts_create
- contacts_update
- contacts_delete
# DENY-BY-DEFAULT: bte removed (hard-rule fix).
# mcp_perplexity intentionally omitted from disclosure until it is
# registered in the live Hermes MCP list and can be introspected.
sovereign_apis: [] # 0 direct HTTP/gRPC calls (per audit §3) sovereign_apis: [] # 0 direct HTTP/gRPC calls (per audit §3)
cortex_tools: [] # steev does not consume cortex/L6-* or cortex/PG-* cortex_tools: [] # steev does not consume cortex/L6-* or cortex/PG-*
credentials: credentials:
- vault_name: google-workspace # Wave 8 (2026-05-24) — aligned with vault per DISCLOSURE-SCHEMA §4.5 (exact-match).
# google-workspace removed (Hermes builtin self-manages OAuth, not in credctl vault).
- vault_name: proton-bridge-imap-user
status: required status: required
scope: read-write scope: read
used_by: [credbridge.sh] used_by: [credbridge.sh]
governance: "JP-personal; Gmail+Calendar+Contacts for briefing + inbox triage" governance: "JP-personal; local Proton Bridge IMAP/SMTP username (himalaya path)"
- vault_name: proton-bridge-imap - vault_name: proton-bridge-imap-pass
status: required status: required
scope: read-write scope: read
used_by: [credbridge.sh] used_by: [credbridge.sh]
governance: "JP-personal; local Proton Bridge IMAP/SMTP (himalaya path)" governance: "JP-personal; local Proton Bridge IMAP/SMTP password (himalaya path)"
- vault_name: perplexity-api - vault_name: perplexity
status: optional status: optional
scope: read scope: read
used_by: [credbridge.sh] used_by: [credbridge.sh]
governance: "JP-personal; WebSearch fallback (MCP path preferred)" governance: "JP-personal; WebSearch fallback (MCP path preferred)"
- vault_name: proton-account-email
status: required
scope: read
used_by: [credbridge.sh, mcp_proton_email]
governance: "JP-personal; Proton account email (consumed by proton-email MCP server)"
- vault_name: proton-account-password
status: required
scope: read
used_by: [credbridge.sh, mcp_proton_email]
governance: "JP-personal; Proton account password (consumed by proton-email MCP server)"
- vault_name: proton-mailbox-password
status: required
scope: read
used_by: [credbridge.sh, mcp_proton_email]
governance: "JP-personal; Proton mailbox E2E key for mail decryption"
+92
View File
@@ -0,0 +1,92 @@
---
name: proton-tools
description: "When Steev needs to access JP's Proton account — Calendar, Mail, Contacts, or explicitly requested Proton Drive checks via rclone. Use this skill to discover which tool answers the user's question, and how to call it. Covers all 24 Proton MCP tools across the three cortex MCP servers (proton-calendar, proton-email, proton-contacts). Triggers: any request involving JP's calendar (events, meetings, availability), mail (inbox, send, reply, search, folders), contacts (lookup, add, search), or Drive via rclone."
metadata:
version: 1.0.0
hermes:
requires_mcp_servers: [proton-calendar, proton-email, proton-contacts]
---
# Proton Tools — Calendar + Mail + Contacts
Authoritative reference for the 24 tools exposed by three cortex MCP servers — `proton-calendar` (8 tools), `proton-email` (10 tools), `proton-contacts` (6 tools). Each MCP facade dials a long-running gRPC gate that holds the Proton session.
## Hard rules
- **Drive is out of scope for Proton MCP tools.** There is no `drive_*` MCP tool. If the user explicitly asks to check Drive via `rclone`, use the live Proton Drive rclone remote instead of claiming no access: this Steev/Hermes profile sets `HOME=/home/svrnty/.hermes/profiles/steev/home`, so plain `rclone` sees the profile config; the working Proton Drive config is `/home/svrnty/.config/rclone/rclone.conf` with remote `proton:`. Use read-only probes first (`rclone --config /home/svrnty/.config/rclone/rclone.conf about proton: --json`) and do not list file names unless JP asks.
- **Destructive tools require explicit confirmation.** `email_send`, `email_reply`, `email_forward`, `calendar_delete`, `contacts_delete`. Never call these without quoting back the action + target + asking JP to confirm.
- **Calendar date filters:** the MCP schema may advertise RFC3339, but `calendar_events`/underlying gate expects date-only filters (`YYYY-MM-DD`) for reliable results. RFC3339 ranges can return empty even when events exist. Convert relative dates ("tomorrow", "next Tuesday") into `YYYY-MM-DD` for list/search filters; keep event create/update timestamps RFC3339.
- **Pagination**: `email_list`, `calendar_events`, `contacts_list` are paginated. Default page size is small (~20). Fetch additional pages only when the user asks for more.
## When to use which tool
### Calendar (8 tools)
| User intent | Tool |
|---|---|
| "What calendars do I have?" | `calendar_list` |
| "What's on my calendar today/this week?" | `calendar_events` with date range |
| "What's coming up?" "Next few meetings?" | `calendar_upcoming` |
| "Find meetings about X" | `calendar_search` |
| "Show me details of [event]" | `calendar_event_get` |
| "Schedule a meeting with…" | `calendar_create` (confirm first) |
| "Move my 3pm to 4pm" | `calendar_update` |
| "Cancel my 3pm" | `calendar_delete` (DESTRUCTIVE — confirm) |
### Mail (10 tools)
| User intent | Tool |
|---|---|
| "How many unread?" "What folders?" | `email_folders` |
| "Show me my inbox" "Latest emails" | `email_list` (folder=INBOX) |
| "Open that email" | `email_read` by UID |
| "Search inbox for…" | `email_search` |
| "Send an email to…" | `email_send` (DESTRUCTIVE — draft + confirm) |
| "Reply to that" | `email_reply` (DESTRUCTIVE — draft + confirm) |
| "Forward this to…" | `email_forward` (DESTRUCTIVE — confirm) |
| "Archive that" | `email_archive` |
| "Mark as read/unread" | `email_mark_read` / `email_mark_unread` |
### Contacts (6 tools)
| User intent | Tool |
|---|---|
| "Who do I have in contacts?" | `contacts_list` |
| "Look up [person]" | `contacts_search` |
| "Pull up [person]'s details" | `contacts_get` |
| "Add [person] to contacts" | `contacts_create` |
| "Update [person]'s email/phone" | `contacts_update` |
| "Remove [person]" | `contacts_delete` (DESTRUCTIVE — confirm) |
## Daily briefing — tool order
When JP asks for the morning briefing, query in this order:
1. `calendar_upcoming` (hours=24) → events today
2. `email_folders` → unread counts
3. `email_list` (folder=INBOX, limit=10) → recent inbox
4. `email_search` (folder=INBOX, query="from:important-person OR is:flagged") → priorities
Don't dump raw output. Synthesize. Lead with what's actionable in JP's voice.
## Search composition
For broad questions like "anything from [person] this week":
- `email_search` (folder=INBOX, query="from:<person>")
- `calendar_search` (query="<person>")
- `contacts_search` (query="<person>")
Run in parallel. Merge results. Group by source.
## Error handling
- **"WaitReady timeout"** → proton connector still booting. Retry once after 2-3s. If still failing, say so + suggest JP check `hermes mcp test proton`.
- **403 / scope error** → proton session expired. Tool handler should re-auth automatically; if not, JP needs to re-run setup.
- **Network / 5xx** → transient. Retry once. If persistent, report and stop.
- **`calendar_create` timeout** → do not retry blindly. First verify the target date range with `calendar_events` using `YYYY-MM-DD` filters to avoid duplicate events. If the event is still absent, one direct gate fallback may be attempted. If creates keep timing out while reads work, refresh `sdo-calendar-gate`: `docker restart sdo-calendar-gate`, wait for `connected to Proton` + `calendar-gate gRPC server listening`, then retry once. If restart fails with a bind-mount error because `/home/svrnty/workspaces/cortex/svrnty.sdo-agents/config/calendar-gate.toml` is a directory, replace it with a symlink to `../../L3-svrnty.agents-fleet/config/calendar-gate.toml`, then `docker start sdo-calendar-gate`.
## What NOT to do
- Don't paginate aggressively — fetch one page, summarize, ask if JP wants more.
- Don't auto-send drafts. Even after JP says "send" once, re-quote subject + recipient on the next compose.
- Don't synthesize calendar events from email content unless JP explicitly asks ("add this to my calendar").
- Don't enumerate every contact when JP asks "who's [person]" — use search, not list.
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""Validate Steev Profile child workspace shell."""
from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REQUIRED = ["AGENTS.md", "README.md", "WORKBOARD.yaml"]
def main() -> int:
errors: list[str] = []
for rel in REQUIRED:
if not (ROOT / rel).exists():
errors.append(f"missing:{rel}")
board = ROOT / "WORKBOARD.yaml"
if board.exists():
text = board.read_text(encoding="utf-8")
for snippet in ["STEEV-WORK-001", "status: candidate", "owner: jp"]:
if snippet not in text:
errors.append(f"workboard_missing:{snippet}")
agents = ROOT / "AGENTS.md"
if agents.exists():
text = agents.read_text(encoding="utf-8")
for snippet in ["child-local", "not Cortex OS Core authority", "python3 tools/validate_steev_child.py"]:
if snippet not in text:
errors.append(f"agents_missing:{snippet}")
result = {"ok": not errors, "validator": "steev-child-v1", "checked": REQUIRED, "errors": errors, "warnings": []}
print(json.dumps(result, indent=2, sort_keys=True))
return 0 if result["ok"] else 1
if __name__ == "__main__":
raise SystemExit(main())