feat(manifest): trusted agent forge identity and guidance
test / unit (pull_request) Successful in 49s
lint / lint (push) Failing after 1m3s
test / image-input-builds (pull_request) Successful in 1m3s
test / integration-docker (pull_request) Successful in 1m9s
refresh-image-locks / refresh (push) Successful in 1m26s
test / coverage (pull_request) Failing after 16s
tracker-policy-pr / check-pr (pull_request) Successful in 5s

Implements PRD prd-new-trusted-agent-forge-identity. Moves author identity
and named forge configurations onto the trusted, host-only agent definition,
and lets a bottle repository optionally associate a git-gate repo with one of
the agent's forge aliases.

- Agent-owned identity: new `author` (name/email) and `forge-accounts`
  (alias -> canonical Gitea /api/v1 origin + host `token_secret` ref) on the
  agent manifest. `author` populates the bottle's git user.name/user.email.
- Remove `git-gate.user` from both agents and bottles; fail with a migration
  pointer to `author`. `git-gate` is no longer accepted on an agent.
- Bottle git-gate repos gain optional `forge: <alias>`, resolved against the
  selected agent's forge-accounts at composition (fail-closed on unknown).
- Fail-closed Gitea API URL validation (https, no userinfo/query/fragment,
  /api/v1 base, host lowercased, trailing slash normalized, host dedup).
- Proxy-held credential: synthesize one inspected, token-authenticated egress
  route per referenced forge alias, scoped to the origin + API prefix. The
  token is resolved from the host env at launch into the egress proxy only —
  never the bottle env, prompt, gitconfig, or workspace.
- Generated, non-secret forge workflow guidance appended to the agent prompt
  for associated repos (API base, branch-backed PR flow, AGit-ref prohibition,
  mutation verification); omitted when no repo declares a forge.
- Agents become home-only: cwd `.bot-bottle/agents` no longer contributes,
  overrides, or is selectable; warned-and-ignored like cwd bottles.
- Docs: README examples, PRD 0011 supersession note, manifest schema docstring.
- Tests: new test_forge_identity suite; legacy git-gate.user/cwd tests updated
  to the agent-owned identity model.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 00:20:14 +00:00
parent db25564bd9
commit 2fafeef61c
25 changed files with 1237 additions and 439 deletions
+14 -7
View File
@@ -110,14 +110,16 @@ class TestAgentFileParses(_ResolveCase):
self.assertFalse(a.prompt.endswith("\n"))
class TestCwdAgentOverridesHome(_ResolveCase):
"""SC #3: a cwd agent file with the same name as a home agent
wins. The home bottle stays intact."""
class TestCwdAgentIgnoredHomeWins(_ResolveCase):
"""SC #3 (revised, PRD prd-new-trusted-agent-forge-identity): agents
are home-only. A cwd agent file with the same name as a home agent no
longer wins — it is warned-and-ignored, so the HOME agent's prompt is
used and the home bottle stays intact."""
def test_cwd_wins(self):
def test_home_wins_cwd_ignored(self):
_write(self.home_cb / "bottles" / "dev.md", _BOTTLE_DEV)
_write(self.home_cb / "agents" / "implementer.md", _AGENT_IMPL)
# Cwd overrides with a different prompt
# Cwd agent with a different prompt is ignored entirely.
_write(
self.cwd_cb / "agents" / "implementer.md",
"""
@@ -129,14 +131,19 @@ class TestCwdAgentOverridesHome(_ResolveCase):
""",
)
m = self.resolve().load_for_agent("implementer")
self.assertIn("CWD-OVERRIDE-PROMPT", m.agent.prompt)
# Home agent's body is used; the cwd override never applies.
self.assertIn("feature implementation agent", m.agent.prompt)
self.assertNotIn("CWD-OVERRIDE-PROMPT", m.agent.prompt)
# Home bottle still present with its two egress routes
self.assertEqual(2, len(m.bottle.egress.routes))
class TestCwdBottlesIgnored(_ResolveCase):
"""SC #4: a bottles/ dir under $CWD is ignored (with a warn).
The home bottle still wins; cwd contributes only agents."""
The home bottle still wins. Under
PRD prd-new-trusted-agent-forge-identity a cwd agents/ dir is also
ignored, so $CWD contributes nothing — the filesystem layout is the
trust boundary."""
def test_ignored(self):
_write(self.home_cb / "bottles" / "dev.md", _BOTTLE_DEV)