From 7ffb131c84e754c5b37e1ea61a919391f80e01b5 Mon Sep 17 00:00:00 2001 From: mayor Date: Mon, 19 Jan 2026 14:52:49 -0800 Subject: [PATCH] feat(security): add GIT_AUTHOR_EMAIL per agent type Phase 1 of agent security model: Set distinct email addresses for each agent type to improve audit trail clarity. Email format: - Town-level: {role}@gastown.local (mayor, deacon, boot) - Rig-level: {rig}-{role}@gastown.local (witness, refinery) - Named agents: {rig}-{role}-{name}@gastown.local (polecat, crew) This makes git log filtering by agent type trivial and provides a foundation for per-agent key separation in future phases. Refs: hq-biot --- internal/config/env.go | 7 +++++++ internal/config/env_test.go | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/internal/config/env.go b/internal/config/env.go index e5ea6f87..067c8731 100644 --- a/internal/config/env.go +++ b/internal/config/env.go @@ -49,36 +49,43 @@ func AgentEnv(cfg AgentEnvConfig) map[string]string { case "mayor": env["BD_ACTOR"] = "mayor" env["GIT_AUTHOR_NAME"] = "mayor" + env["GIT_AUTHOR_EMAIL"] = "mayor@gastown.local" case "deacon": env["BD_ACTOR"] = "deacon" env["GIT_AUTHOR_NAME"] = "deacon" + env["GIT_AUTHOR_EMAIL"] = "deacon@gastown.local" case "boot": env["BD_ACTOR"] = "deacon-boot" env["GIT_AUTHOR_NAME"] = "boot" + env["GIT_AUTHOR_EMAIL"] = "boot@gastown.local" case "witness": env["GT_RIG"] = cfg.Rig env["BD_ACTOR"] = fmt.Sprintf("%s/witness", cfg.Rig) env["GIT_AUTHOR_NAME"] = fmt.Sprintf("%s/witness", cfg.Rig) + env["GIT_AUTHOR_EMAIL"] = fmt.Sprintf("%s-witness@gastown.local", cfg.Rig) case "refinery": env["GT_RIG"] = cfg.Rig env["BD_ACTOR"] = fmt.Sprintf("%s/refinery", cfg.Rig) env["GIT_AUTHOR_NAME"] = fmt.Sprintf("%s/refinery", cfg.Rig) + env["GIT_AUTHOR_EMAIL"] = fmt.Sprintf("%s-refinery@gastown.local", cfg.Rig) case "polecat": env["GT_RIG"] = cfg.Rig env["GT_POLECAT"] = cfg.AgentName env["BD_ACTOR"] = fmt.Sprintf("%s/polecats/%s", cfg.Rig, cfg.AgentName) env["GIT_AUTHOR_NAME"] = cfg.AgentName + env["GIT_AUTHOR_EMAIL"] = fmt.Sprintf("%s-polecat-%s@gastown.local", cfg.Rig, cfg.AgentName) case "crew": env["GT_RIG"] = cfg.Rig env["GT_CREW"] = cfg.AgentName env["BD_ACTOR"] = fmt.Sprintf("%s/crew/%s", cfg.Rig, cfg.AgentName) env["GIT_AUTHOR_NAME"] = cfg.AgentName + env["GIT_AUTHOR_EMAIL"] = fmt.Sprintf("%s-crew-%s@gastown.local", cfg.Rig, cfg.AgentName) } // Only set GT_ROOT if provided diff --git a/internal/config/env_test.go b/internal/config/env_test.go index 12b4a928..7c9293e4 100644 --- a/internal/config/env_test.go +++ b/internal/config/env_test.go @@ -14,6 +14,7 @@ func TestAgentEnv_Mayor(t *testing.T) { assertEnv(t, env, "GT_ROLE", "mayor") assertEnv(t, env, "BD_ACTOR", "mayor") assertEnv(t, env, "GIT_AUTHOR_NAME", "mayor") + assertEnv(t, env, "GIT_AUTHOR_EMAIL", "mayor@gastown.local") assertEnv(t, env, "GT_ROOT", "/town") assertNotSet(t, env, "GT_RIG") assertNotSet(t, env, "BEADS_NO_DAEMON") @@ -31,6 +32,7 @@ func TestAgentEnv_Witness(t *testing.T) { assertEnv(t, env, "GT_RIG", "myrig") assertEnv(t, env, "BD_ACTOR", "myrig/witness") assertEnv(t, env, "GIT_AUTHOR_NAME", "myrig/witness") + assertEnv(t, env, "GIT_AUTHOR_EMAIL", "myrig-witness@gastown.local") assertEnv(t, env, "GT_ROOT", "/town") } @@ -49,6 +51,7 @@ func TestAgentEnv_Polecat(t *testing.T) { assertEnv(t, env, "GT_POLECAT", "Toast") assertEnv(t, env, "BD_ACTOR", "myrig/polecats/Toast") assertEnv(t, env, "GIT_AUTHOR_NAME", "Toast") + assertEnv(t, env, "GIT_AUTHOR_EMAIL", "myrig-polecat-Toast@gastown.local") assertEnv(t, env, "BEADS_AGENT_NAME", "myrig/Toast") assertEnv(t, env, "BEADS_NO_DAEMON", "1") } @@ -68,6 +71,7 @@ func TestAgentEnv_Crew(t *testing.T) { assertEnv(t, env, "GT_CREW", "emma") assertEnv(t, env, "BD_ACTOR", "myrig/crew/emma") assertEnv(t, env, "GIT_AUTHOR_NAME", "emma") + assertEnv(t, env, "GIT_AUTHOR_EMAIL", "myrig-crew-emma@gastown.local") assertEnv(t, env, "BEADS_AGENT_NAME", "myrig/emma") assertEnv(t, env, "BEADS_NO_DAEMON", "1") } @@ -85,6 +89,7 @@ func TestAgentEnv_Refinery(t *testing.T) { assertEnv(t, env, "GT_RIG", "myrig") assertEnv(t, env, "BD_ACTOR", "myrig/refinery") assertEnv(t, env, "GIT_AUTHOR_NAME", "myrig/refinery") + assertEnv(t, env, "GIT_AUTHOR_EMAIL", "myrig-refinery@gastown.local") assertEnv(t, env, "BEADS_NO_DAEMON", "1") } @@ -98,6 +103,7 @@ func TestAgentEnv_Deacon(t *testing.T) { assertEnv(t, env, "GT_ROLE", "deacon") assertEnv(t, env, "BD_ACTOR", "deacon") assertEnv(t, env, "GIT_AUTHOR_NAME", "deacon") + assertEnv(t, env, "GIT_AUTHOR_EMAIL", "deacon@gastown.local") assertEnv(t, env, "GT_ROOT", "/town") assertNotSet(t, env, "GT_RIG") assertNotSet(t, env, "BEADS_NO_DAEMON") @@ -113,6 +119,7 @@ func TestAgentEnv_Boot(t *testing.T) { assertEnv(t, env, "GT_ROLE", "boot") assertEnv(t, env, "BD_ACTOR", "deacon-boot") assertEnv(t, env, "GIT_AUTHOR_NAME", "boot") + assertEnv(t, env, "GIT_AUTHOR_EMAIL", "boot@gastown.local") assertEnv(t, env, "GT_ROOT", "/town") assertNotSet(t, env, "GT_RIG") assertNotSet(t, env, "BEADS_NO_DAEMON")