← back to AbramsEgo
TK-10381: A2A activation runbook (turnkey: exact allowlist schema, env: auth_ref, verify, rollback) + make smoke a2a_allowlist_empty check activation-aware (A2A_EXPECT_PEERS=1 opt-out) so it stops false-alarming on intended activation
8dd6303f87f84a390514092e9005336b14c7c7aa · 2026-08-09 17:11:17 -0700 · Steve
Files touched
A memos/a2a-activation-runbook.mdM scripts/smoke.sh
Diff
commit 8dd6303f87f84a390514092e9005336b14c7c7aa
Author: Steve <steve@designerwallcoverings.com>
Date: Sun Aug 9 17:11:17 2026 -0700
TK-10381: A2A activation runbook (turnkey: exact allowlist schema, env: auth_ref, verify, rollback) + make smoke a2a_allowlist_empty check activation-aware (A2A_EXPECT_PEERS=1 opt-out) so it stops false-alarming on intended activation
---
memos/a2a-activation-runbook.md | 85 +++++++++++++++++++++++++++++++++++++++++
scripts/smoke.sh | 11 +++++-
2 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/memos/a2a-activation-runbook.md b/memos/a2a-activation-runbook.md
new file mode 100644
index 00000000..f81553c3
--- /dev/null
+++ b/memos/a2a-activation-runbook.md
@@ -0,0 +1,85 @@
+# A2A activation runbook — how to turn the external-consult client ON (TK-10381)
+
+The A2A client is BUILT and ships **OFF**: the allowlist `data/a2a-agents.json` is `[]`, so
+every `/api/a2a/consult` is refused and **zero outbound connection is ever attempted**.
+Turning it on is a single Steve-gated edit. This is the turnkey procedure.
+
+> Gate reminder: activation is Steve's call. Only add a peer you TRUST — every response it
+> returns is treated as hostile input (rendered escaped + UNTRUSTED, never fed to an agent),
+> but the whole point of an allowlist is that you vouch for the host you're talking to.
+
+## 1. Add ONE peer to the allowlist
+
+Edit `~/Projects/AbramsEgo/data/a2a-agents.json` (starts `[]`). Each entry:
+
+```json
+[
+ {
+ "name": "acme-advisor",
+ "url": "https://agent.acme.example.com",
+ "purpose": "monetization/affiliate best-practice consults",
+ "auth_ref": "env:A2A_ACME_TOKEN"
+ }
+]
+```
+
+Field rules (enforced by the client):
+- **name** — the lookup key you pass as `{ "agent": "<name>" }`. Keep it short/unique.
+- **url** — the peer's BASE url. **MUST be `https://` on port 443** (no http, no custom port,
+ no embedded `user:pass@`). The client fetches `<url>/.well-known/agent-card.json` and pins
+ every RPC call to THIS host (a hostile card can't redirect it elsewhere).
+- **purpose** — free text; shows in the dashboard picker + `/api/a2a/agents`.
+- **auth_ref** — OPTIONAL. Prefer **`env:VARNAME`** — the token is read from the process env
+ (populated by secrets-manager), NOT stored in this JSON file. A bare literal is accepted
+ but discouraged (don't put a live key in a repo file). Omit entirely for no-auth peers.
+
+If you used `env:A2A_ACME_TOKEN`, add that secret via the `secrets` skill so it lands in the
+AbramsEgo `.env` / process env — never paste the raw token into `a2a-agents.json`.
+
+## 2. Load it
+
+```sh
+pm2 restart abramsego --update-env # picks up the new allowlist + any new env token
+```
+
+## 3. Verify
+
+```sh
+cd ~/Projects/AbramsEgo && bash scripts/smoke.sh
+```
+
+Note: the smoke test guards the CLOSED gate by default (asserts an empty allowlist to catch an
+*unintended* opening). Once you've intentionally added a peer, run it with **`A2A_EXPECT_PEERS=1`**
+so that check becomes an informational NOTE instead of a failure:
+
+```sh
+A2A_EXPECT_PEERS=1 bash scripts/smoke.sh
+```
+
+The two gate-MECHANISM assertions (unlisted-agent refused, payload-linter armed) always run and
+must still pass.
+
+Then confirm the peer is live:
+```sh
+curl -s -u admin:DW2024! http://127.0.0.1:9773/api/a2a/agents # lists your peer
+curl -s -u admin:DW2024! -X POST -H 'Content-Type: application/json' \
+ -d '{"agent":"acme-advisor","q":"what is a good affiliate cookie window?"}' \
+ http://127.0.0.1:9773/api/a2a/consult # returns answer_untrusted
+```
+
+The dashboard "🛰️ A2A Consult" panel (at :9773) will now show the peer in its dropdown and
+render answers tagged **UNTRUSTED · EXTERNAL**.
+
+## 4. Rollback (turn it back OFF)
+
+Set `data/a2a-agents.json` back to `[]` and `pm2 restart abramsego`. Every consult refuses
+again; no code change needed.
+
+## What stays gated / out of scope
+- **A2A SERVER mode** (publishing our own Agent Card, opening an inbound port) is a SEPARATE,
+ default-BLOCK decision — it would make :9773 a public attack surface. Not built.
+- Acting on a consult's advice: any recommended ACTION drafts to
+ `~/.claude/yolo-queue/pending-approval/` for officer/Steve review — A2A advice never
+ auto-executes and never enters `/api/chat` or an agent prompt.
+
+Security rails reference: `memos/a2a-egress-rails.md`. Client: `lib/a2a-client.js`.
diff --git a/scripts/smoke.sh b/scripts/smoke.sh
index 6f21301e..fe3201c9 100755
--- a/scripts/smoke.sh
+++ b/scripts/smoke.sh
@@ -72,8 +72,15 @@ SMOKE_RC=$?
# regressed (someone opened the allowlist or dropped the linter) — investigate.
A2A_AGENTS=$(curl -s -m 8 -u "$AUSER:$APASS" "$BASE/api/a2a/agents")
-echo "$A2A_AGENTS" | grep -q '"agents":\[\]' || {
- echo "FAIL a2a_allowlist_empty — expected empty allowlist (gate open?), got: $A2A_AGENTS"; exit 1; }
+# By default the gate must be CLOSED (empty allowlist). Once Steve intentionally
+# activates a peer (see memos/a2a-activation-runbook.md), set A2A_EXPECT_PEERS=1
+# so this stops false-alarming — the two gate-MECHANISM checks below still run.
+if [ "${A2A_EXPECT_PEERS:-0}" = "1" ]; then
+ echo "NOTE a2a_allowlist — peers intended (A2A_EXPECT_PEERS=1): $A2A_AGENTS"
+else
+ echo "$A2A_AGENTS" | grep -q '"agents":\[\]' || {
+ echo "FAIL a2a_allowlist_empty — allowlist non-empty but A2A_EXPECT_PEERS!=1 (gate opened unexpectedly?): $A2A_AGENTS"; exit 1; }
+fi
A2A_REFUSE=$(curl -s -m 8 -u "$AUSER:$APASS" -X POST -H 'Content-Type: application/json' \
-d '{"agent":"__smoke_nobody__","q":"ping"}' "$BASE/api/a2a/consult")
← 39bb3f32 auto-data-snapshot: 2026-08-09T16:57:53 (1 data files) — dat
·
back to AbramsEgo
·
auto-data-snapshot: 2026-08-09T17:28:38 (1 data files) — dat 0b6ecde1 →