Files
bot-bottle/tests/unit/test_manifest_lazy_loader.py
T
didericis-claude 2fafeef61c
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
feat(manifest): trusted agent forge identity and guidance
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>
2026-07-27 00:39:55 +00:00

118 lines
3.8 KiB
Python

"""Unit: lazy (on-disk) ManifestIndex loader branches (coverage ratchet).
The eager from_json_obj path is covered by test_manifest_validation.py;
this drives the lazy resolve()/from_md_dirs path — all_agent_names with a
cwd overlay, load_for_agent on an unknown / malformed agent file, and
require_agent's names-only file-existence checks — so manifest.py's
core-module coverage doesn't depend on the integration suite."""
from __future__ import annotations
import os
import shutil
import tempfile
import textwrap
import unittest
from pathlib import Path
from bot_bottle.manifest import ManifestError, ManifestIndex
def _write(p: Path, text: str) -> None:
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(textwrap.dedent(text).lstrip("\n"))
_BOTTLE_DEV = """
---
egress:
routes:
- host: example.com
---
The dev bottle.
"""
_AGENT = """
---
bottle: dev
---
An agent.
"""
# Tab in the frontmatter indent -> YamlSubsetError on parse.
_AGENT_BAD_FM = "---\nskills:\n\t- x\n---\nbody\n"
class _LazyCase(unittest.TestCase):
def setUp(self) -> None:
self.home_root = Path(tempfile.mkdtemp(prefix="cb-home-"))
self.cwd_root = Path(tempfile.mkdtemp(prefix="cb-cwd-"))
self._orig_home = os.environ.get("HOME")
os.environ["HOME"] = str(self.home_root)
def tearDown(self) -> None:
if self._orig_home is None:
os.environ.pop("HOME", None)
else:
os.environ["HOME"] = self._orig_home
shutil.rmtree(self.home_root, ignore_errors=True)
shutil.rmtree(self.cwd_root, ignore_errors=True)
@property
def home_cb(self) -> Path:
return self.home_root / ".bot-bottle"
@property
def cwd_cb(self) -> Path:
return self.cwd_root / ".bot-bottle"
def resolve(self) -> ManifestIndex:
return ManifestIndex.resolve(str(self.cwd_root))
class TestAllAgentNamesLazy(_LazyCase):
def test_cwd_agents_ignored_home_only(self) -> None:
# Agents are home-only (PRD prd-new-trusted-agent-forge-identity):
# a cwd agents/ dir is warned-and-ignored, so only the home agent
# appears in all_agent_names.
_write(self.home_cb / "bottles" / "dev.md", _BOTTLE_DEV)
_write(self.home_cb / "agents" / "alpha.md", _AGENT)
_write(self.cwd_cb / "agents" / "beta.md", _AGENT)
self.assertEqual(["alpha"], self.resolve().all_agent_names)
class TestLoadForAgentLazy(_LazyCase):
def test_unknown_agent_raises(self) -> None:
_write(self.home_cb / "agents" / "alpha.md", _AGENT)
with self.assertRaises(ManifestError):
self.resolve().load_for_agent("nope")
def test_malformed_frontmatter_raises(self) -> None:
_write(self.home_cb / "bottles" / "dev.md", _BOTTLE_DEV)
_write(self.home_cb / "agents" / "broken.md", _AGENT_BAD_FM)
with self.assertRaises(ManifestError):
self.resolve().load_for_agent("broken")
class TestRequireAgentLazy(_LazyCase):
def test_existing_home_agent_ok(self) -> None:
_write(self.home_cb / "agents" / "alpha.md", _AGENT)
self.resolve().require_agent("alpha") # no raise
def test_cwd_only_agent_not_selectable(self) -> None:
# Agents are home-only (PRD prd-new-trusted-agent-forge-identity):
# a cwd-only agent file is never selectable, so require_agent raises.
_write(self.home_cb / "agents" / "alpha.md", _AGENT)
_write(self.cwd_cb / "agents" / "beta.md", _AGENT)
with self.assertRaises(ManifestError):
self.resolve().require_agent("beta")
def test_unknown_agent_raises(self) -> None:
_write(self.home_cb / "agents" / "alpha.md", _AGENT)
with self.assertRaises(ManifestError):
self.resolve().require_agent("nope")
if __name__ == "__main__":
unittest.main()