From 4cf57f55bbdd129340cc7a75238239593aced314 Mon Sep 17 00:00:00 2001 From: claude Date: Mon, 27 Jul 2026 02:07:20 +0000 Subject: [PATCH] docs(prd): assign PRD number 0082 CI (prd-number-check) rejects unnumbered prd-new-*.md on merge to main. Rename docs/prds/prd-new-trusted-agent-forge-identity.md to 0082-trusted-agent-forge-identity.md (0081 is claimed by #517/#519) and update the in-repo 'PRD prd-new-trusted-agent-forge-identity' citations to 'PRD 0082'. Co-Authored-By: Claude Opus 4.8 --- README.md | 2 +- bot_bottle/backend/resolve_common.py | 4 ++-- bot_bottle/cli/commands/start.py | 2 +- bot_bottle/egress/service.py | 2 +- bot_bottle/manifest/__init__.py | 2 +- bot_bottle/manifest/agent.py | 4 ++-- bot_bottle/manifest/bottle.py | 2 +- bot_bottle/manifest/forge.py | 2 +- bot_bottle/manifest/git.py | 6 +++--- bot_bottle/manifest/index.py | 12 ++++++------ bot_bottle/manifest/schema.py | 2 +- docs/prds/0011-per-file-md-manifest.md | 2 +- ...ntity.md => 0082-trusted-agent-forge-identity.md} | 2 +- tests/unit/test_forge_identity.py | 2 +- tests/unit/test_manifest_agent_git_user.py | 2 +- tests/unit/test_manifest_extends.py | 4 ++-- tests/unit/test_manifest_git_user.py | 2 +- tests/unit/test_manifest_lazy_loader.py | 4 ++-- tests/unit/test_manifest_md_load.py | 4 ++-- 19 files changed, 31 insertions(+), 31 deletions(-) rename docs/prds/{prd-new-trusted-agent-forge-identity.md => 0082-trusted-agent-forge-identity.md} (99%) diff --git a/README.md b/README.md index 5f5d002e..31802531 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ BOT_BOTTLE_BACKEND=firecracker ./cli.py start ## Manifest -Bottles and agents are Markdown files with YAML frontmatter under `~/.bot-bottle/`. The Markdown body is the system prompt. Both bottles and agents are **home-only**: they live under `~/.bot-bottle/bottles/` and `~/.bot-bottle/agents/`. A `/.bot-bottle/agents/.md` shipped by a workspace is ignored with a warning — since an agent may select a host identity and forge secret, checked-out content must not define one (PRD prd-new-trusted-agent-forge-identity). Keep repo-specific behavioral instructions in `AGENTS.md` instead. +Bottles and agents are Markdown files with YAML frontmatter under `~/.bot-bottle/`. The Markdown body is the system prompt. Both bottles and agents are **home-only**: they live under `~/.bot-bottle/bottles/` and `~/.bot-bottle/agents/`. A `/.bot-bottle/agents/.md` shipped by a workspace is ignored with a warning — since an agent may select a host identity and forge secret, checked-out content must not define one (PRD 0082). Keep repo-specific behavioral instructions in `AGENTS.md` instead. Identity is **agent-owned**: the author name/email and named forge accounts live on the agent, not under `git-gate` (which now carries only Git transport policy). A bottle repo may optionally name one of the selected agent's forge aliases via `forge:`. diff --git a/bot_bottle/backend/resolve_common.py b/bot_bottle/backend/resolve_common.py index 628be3c7..34d806f2 100644 --- a/bot_bottle/backend/resolve_common.py +++ b/bot_bottle/backend/resolve_common.py @@ -76,7 +76,7 @@ def prepare_agent_state_dir(slug: str, manifest: Manifest) -> tuple[Path, Path]: For repositories associated with a forge, appends generated, non-secret provider-specific workflow guidance to the prompt (PRD - prd-new-trusted-agent-forge-identity). The guidance carries neither the + 0082). The guidance carries neither the token value nor its `token_secret` name.""" agent = manifest.agent agent_dir = agent_state_dir(slug) @@ -102,7 +102,7 @@ def prepare_egress( ) -> EgressPlan: """Build the egress plan, adding a scoped, proxy-held Gitea API route for each forge alias referenced by a selected git-gate repo (PRD - prd-new-trusted-agent-forge-identity). The token is resolved from the host + 0082). The token is resolved from the host env at launch and never enters the bottle.""" egress_dir = egress_state_dir(slug) egress_dir.mkdir(parents=True, exist_ok=True) diff --git a/bot_bottle/cli/commands/start.py b/bot_bottle/cli/commands/start.py index 580366e7..1a471ba8 100644 --- a/bot_bottle/cli/commands/start.py +++ b/bot_bottle/cli/commands/start.py @@ -383,7 +383,7 @@ def _peek_agent_bottle(manifest: ManifestIndex, agent_name: str) -> str: from ...manifest.loader import scan_agent_names from ...yaml_subset import YamlSubsetError, parse_frontmatter - # Agents are home-only (PRD prd-new-trusted-agent-forge-identity). + # Agents are home-only (PRD 0082). home_agents = scan_agent_names(manifest.home_md / "agents") path = home_agents.get(agent_name) if path is None: diff --git a/bot_bottle/egress/service.py b/bot_bottle/egress/service.py index ffe2da49..6cb1e527 100644 --- a/bot_bottle/egress/service.py +++ b/bot_bottle/egress/service.py @@ -124,7 +124,7 @@ def egress_forge_routes( ) -> tuple[EgressRoute, ...]: """Synthesize one inspected, token-authenticated egress route per distinct forge alias referenced by a selected git-gate repo (PRD - prd-new-trusted-agent-forge-identity). + 0082). The route is scoped to the canonical forge origin (host) and API prefix (`/api/v1`): the proxy injects the Gitea `token` scheme using the value of diff --git a/bot_bottle/manifest/__init__.py b/bot_bottle/manifest/__init__.py index e26d1a6a..bbf2b85a 100644 --- a/bot_bottle/manifest/__init__.py +++ b/bot_bottle/manifest/__init__.py @@ -1,7 +1,7 @@ """Manifest dataclasses (PRD 0011 layout). Reads the per-file manifest tree (home-only — -PRD prd-new-trusted-agent-forge-identity): +PRD 0082): $HOME/.bot-bottle/bottles/.md — one bottle per file $HOME/.bot-bottle/agents/.md — agents diff --git a/bot_bottle/manifest/agent.py b/bot_bottle/manifest/agent.py index 8bb913c0..7bfd9fef 100644 --- a/bot_bottle/manifest/agent.py +++ b/bot_bottle/manifest/agent.py @@ -119,7 +119,7 @@ class ManifestAgent: bottle: str = "" skills: tuple[str, ...] = () prompt: str = "" - # Agent-owned identity (PRD prd-new-trusted-agent-forge-identity). + # Agent-owned identity (PRD 0082). # `author` populates the bottle's git user.name/user.email; # `forge_accounts` maps a forge alias to a canonical Gitea API origin and # a host token reference. Both live only on the agent — never under @@ -138,7 +138,7 @@ class ManifestAgent: if "git-gate" in d: raise ManifestError( f"agent '{name}' has a 'git-gate' block, which is no longer " - f"accepted on an agent (PRD prd-new-trusted-agent-forge-identity). " + f"accepted on an agent (PRD 0082). " f"Move git-gate.user name/email into the 'author' block; " f"git-gate.repos stays on the bottle." ) diff --git a/bot_bottle/manifest/bottle.py b/bot_bottle/manifest/bottle.py index fcf2eef7..e58023bc 100644 --- a/bot_bottle/manifest/bottle.py +++ b/bot_bottle/manifest/bottle.py @@ -109,7 +109,7 @@ class ManifestBottle: # `git_user` is now an internal resolved carrier populated from the # selected agent's `author` at composition time — it is never parsed - # from the bottle manifest (PRD prd-new-trusted-agent-forge-identity). + # from the bottle manifest (PRD 0082). git: tuple[ManifestGitEntry, ...] = () git_user = ManifestGitUser() git_raw = d.get("git-gate") diff --git a/bot_bottle/manifest/forge.py b/bot_bottle/manifest/forge.py index 554c435c..aca675fb 100644 --- a/bot_bottle/manifest/forge.py +++ b/bot_bottle/manifest/forge.py @@ -1,4 +1,4 @@ -"""Agent-owned author identity and forge accounts (PRD prd-new-trusted-agent-forge-identity). +"""Agent-owned author identity and forge accounts (PRD 0082). `ManifestAuthor` is the agent's git author identity (name/email) — it moved off `git-gate.user` (PRD 0027 / ADR 0002) and onto the trusted agent definition. diff --git a/bot_bottle/manifest/git.py b/bot_bottle/manifest/git.py index bf9b2bff..1885eb1f 100644 --- a/bot_bottle/manifest/git.py +++ b/bot_bottle/manifest/git.py @@ -117,7 +117,7 @@ class ManifestGitEntry: UpstreamHost: str = "" UpstreamPort: str = "" UpstreamPath: str = "" - # Optional forge alias (PRD prd-new-trusted-agent-forge-identity). When + # Optional forge alias (PRD 0082). When # set, it must match a `forge-accounts` alias on the selected agent; the # composition enables a scoped proxy-held API credential and forge # workflow guidance for this repo. Empty = no forge association. @@ -309,12 +309,12 @@ def parse_git_gate_config( raw: object, ) -> tuple[ManifestGitEntry, ...]: """Parse `git-gate` on a bottle. Only `repos` is accepted; `git-gate.user` - moved to the agent's `author` block (PRD prd-new-trusted-agent-forge-identity).""" + moved to the agent's `author` block (PRD 0082).""" d = as_json_object(raw, f"bottle '{bottle_name}' git-gate") if "user" in d: raise ManifestError( f"bottle '{bottle_name}' git-gate.user is no longer supported " - f"(PRD prd-new-trusted-agent-forge-identity). Move name/email into " + f"(PRD 0082). Move name/email into " f"the selected home agent's 'author' block:\n" f" author:\n name: \n email: \n" f"Identity is agent-owned; git-gate now carries only transport " diff --git a/bot_bottle/manifest/index.py b/bot_bottle/manifest/index.py index 6d572d18..53c20cfb 100644 --- a/bot_bottle/manifest/index.py +++ b/bot_bottle/manifest/index.py @@ -52,7 +52,7 @@ def _warn_ignored_cwd_dir(cwd_dir: Path, kind: str, home_path: str) -> None: warn( f"ignoring {kind[:-1]} file(s) under {stale}: {names}. " f"{kind.capitalize()} can only live under {home_path} " - f"(PRD prd-new-trusted-agent-forge-identity). Move them or delete." + f"(PRD 0082). Move them or delete." ) @@ -62,7 +62,7 @@ def _compose_manifest( raw_bottle: "ManifestBottle", ) -> "Manifest": """Build the single-value Manifest from the selected agent and its - effective bottle (PRD prd-new-trusted-agent-forge-identity): + effective bottle (PRD 0082): - the agent's `author` populates the bottle's git user.name/user.email; - each git-gate repo's `forge` alias is resolved against the agent's @@ -192,7 +192,7 @@ class ManifestIndex: $HOME/.bot-bottle/agents/.md — agents (home-only) Both agents and bottles are home-only - (PRD prd-new-trusted-agent-forge-identity): a `bottles/` or `agents/` + (PRD 0082): a `bottles/` or `agents/` subdir under $CWD is logged as a warning and ignored — the filesystem layout IS the trust boundary, since an agent may now select a host identity and forge secret. @@ -248,7 +248,7 @@ class ManifestIndex: if cwd_dir is not None: _warn_ignored_cwd_dir(cwd_dir, "bottles", "$HOME/.bot-bottle/bottles/") # Agents became home-only in - # PRD prd-new-trusted-agent-forge-identity: a cwd agent file that + # PRD 0082: a cwd agent file that # once shadowed a home agent could select a host identity/secret, # so it is now ignored with a migration pointer. _warn_ignored_cwd_dir(cwd_dir, "agents", "$HOME/.bot-bottle/agents/") @@ -295,7 +295,7 @@ class ManifestIndex: filenames without reading their content. In eager mode (from from_json_obj) it returns the pre-parsed agents' names. - Agents are home-only (PRD prd-new-trusted-agent-forge-identity): cwd + Agents are home-only (PRD 0082): cwd agent files never contribute names.""" if self.home_md is not None: return sorted(scan_agent_names(self.home_md / "agents").keys()) @@ -351,7 +351,7 @@ class ManifestIndex: """Lazy path (resolve/from_md_dirs): read and parse the agent file and its bottle chain from disk for the first time here.""" assert self.home_md is not None # guaranteed by load_for_agent dispatch - # Agents are home-only (PRD prd-new-trusted-agent-forge-identity): + # Agents are home-only (PRD 0082): # a cwd agent file must not select a host identity or forge secret. home_agents = scan_agent_names(self.home_md / "agents") diff --git a/bot_bottle/manifest/schema.py b/bot_bottle/manifest/schema.py index 87c3bc88..15f6a174 100644 --- a/bot_bottle/manifest/schema.py +++ b/bot_bottle/manifest/schema.py @@ -23,7 +23,7 @@ BOTTLE_KEYS = frozenset( ) AGENT_KEYS_REQUIRED: frozenset[str] = frozenset() # `author` / `forge-accounts` are agent-owned identity (PRD -# prd-new-trusted-agent-forge-identity). `git-gate` is no longer accepted on an +# 0082). `git-gate` is no longer accepted on an # agent: `git-gate.user` moved to `author`, and `git-gate.repos` is bottle-only. AGENT_KEYS_OPTIONAL = frozenset({"bottle", "skills", "author", "forge-accounts"}) diff --git a/docs/prds/0011-per-file-md-manifest.md b/docs/prds/0011-per-file-md-manifest.md index d47e3f1d..2b1ff597 100644 --- a/docs/prds/0011-per-file-md-manifest.md +++ b/docs/prds/0011-per-file-md-manifest.md @@ -4,7 +4,7 @@ - **Author:** didericis - **Created:** 2026-05-24 -> **Superseded in part by PRD prd-new-trusted-agent-forge-identity.** +> **Superseded in part by PRD 0082.** > The `$CWD/.bot-bottle/agents/.md` discovery/override path described > below is removed: agents are now **home-only**, like bottles. Once an agent > definition can select a host identity (`author`) and a host forge secret diff --git a/docs/prds/prd-new-trusted-agent-forge-identity.md b/docs/prds/0082-trusted-agent-forge-identity.md similarity index 99% rename from docs/prds/prd-new-trusted-agent-forge-identity.md rename to docs/prds/0082-trusted-agent-forge-identity.md index 5d4f96b2..6b7f6473 100644 --- a/docs/prds/prd-new-trusted-agent-forge-identity.md +++ b/docs/prds/0082-trusted-agent-forge-identity.md @@ -1,4 +1,4 @@ -# PRD prd-new: Trusted agent forge identity and guidance +# PRD 0082: Trusted agent forge identity and guidance - **Status:** Accepted - **Author:** didericis-claude diff --git a/tests/unit/test_forge_identity.py b/tests/unit/test_forge_identity.py index d2474b84..49b50352 100644 --- a/tests/unit/test_forge_identity.py +++ b/tests/unit/test_forge_identity.py @@ -1,5 +1,5 @@ """Unit: trusted agent forge identity & guidance -(PRD prd-new-trusted-agent-forge-identity). +(PRD 0082). Covers the net-new surface: agent `author`, agent `forge-accounts` (Gitea API URL validation + host token reference), the repo→forge association resolved at diff --git a/tests/unit/test_manifest_agent_git_user.py b/tests/unit/test_manifest_agent_git_user.py index 9fb09915..f66a895f 100644 --- a/tests/unit/test_manifest_agent_git_user.py +++ b/tests/unit/test_manifest_agent_git_user.py @@ -1,5 +1,5 @@ """Unit: agent-owned identity via `author` (PRD -prd-new-trusted-agent-forge-identity). +0082). Identity is agent-only now: an agent file declares an `author` block (name + email, both required) and at `ManifestIndex.load_for_agent()` diff --git a/tests/unit/test_manifest_extends.py b/tests/unit/test_manifest_extends.py index afaebb10..220cae8b 100644 --- a/tests/unit/test_manifest_extends.py +++ b/tests/unit/test_manifest_extends.py @@ -132,7 +132,7 @@ class TestExtendsEnvMerge(unittest.TestCase): class TestExtendsGitMerge(unittest.TestCase): """git-gate.repos merges by name, with same-name child entries merging field-by-field (child wins). Bottles no longer carry a user - identity (PRD prd-new-trusted-agent-forge-identity), so only repos + identity (PRD 0082), so only repos merging is meaningful across extends chains.""" _GIT_ENTRY_A = {"url": "ssh://git@host-a/a.git", "key": {"provider": "static", "path": "/dev/null"}} @@ -339,7 +339,7 @@ class TestExtendsEgressMerge(unittest.TestCase): class TestExtendsNoBottleUserIdentity(unittest.TestCase): """Bottles no longer carry a user identity (PRD - prd-new-trusted-agent-forge-identity): identity moved to the agent's + 0082): identity moved to the agent's `author` block. `git-gate.user` on a bottle is rejected outright, and `git_user` is always empty across an extends chain.""" diff --git a/tests/unit/test_manifest_git_user.py b/tests/unit/test_manifest_git_user.py index a3559ea2..e9f64cfc 100644 --- a/tests/unit/test_manifest_git_user.py +++ b/tests/unit/test_manifest_git_user.py @@ -1,5 +1,5 @@ """Unit: agent `author` identity -> bottle.git_user (PRD -prd-new-trusted-agent-forge-identity). +0082). Identity moved off `git-gate.user` (bottle) onto the trusted agent's `author` block. At `load_for_agent` the agent's author populates the diff --git a/tests/unit/test_manifest_lazy_loader.py b/tests/unit/test_manifest_lazy_loader.py index c42992c4..fd17c0a1 100644 --- a/tests/unit/test_manifest_lazy_loader.py +++ b/tests/unit/test_manifest_lazy_loader.py @@ -72,7 +72,7 @@ class _LazyCase(unittest.TestCase): class TestAllAgentNamesLazy(_LazyCase): def test_cwd_agents_ignored_home_only(self) -> None: - # Agents are home-only (PRD prd-new-trusted-agent-forge-identity): + # Agents are home-only (PRD 0082): # 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) @@ -100,7 +100,7 @@ class TestRequireAgentLazy(_LazyCase): 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): + # Agents are home-only (PRD 0082): # 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) diff --git a/tests/unit/test_manifest_md_load.py b/tests/unit/test_manifest_md_load.py index 3e657e24..e6397e0a 100644 --- a/tests/unit/test_manifest_md_load.py +++ b/tests/unit/test_manifest_md_load.py @@ -111,7 +111,7 @@ class TestAgentFileParses(_ResolveCase): class TestCwdAgentIgnoredHomeWins(_ResolveCase): - """SC #3 (revised, PRD prd-new-trusted-agent-forge-identity): agents + """SC #3 (revised, PRD 0082): 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.""" @@ -141,7 +141,7 @@ class TestCwdAgentIgnoredHomeWins(_ResolveCase): class TestCwdBottlesIgnored(_ResolveCase): """SC #4: a bottles/ dir under $CWD is ignored (with a warn). The home bottle still wins. Under - PRD prd-new-trusted-agent-forge-identity a cwd agents/ dir is also + PRD 0082 a cwd agents/ dir is also ignored, so $CWD contributes nothing — the filesystem layout is the trust boundary."""