diff --git a/docs/prds/prd-new-trusted-agent-forge-identity.md b/docs/prds/prd-new-trusted-agent-forge-identity.md new file mode 100644 index 00000000..60ce086b --- /dev/null +++ b/docs/prds/prd-new-trusted-agent-forge-identity.md @@ -0,0 +1,391 @@ +# PRD prd-new: Trusted agent forge identity and guidance + +- **Status:** Draft +- **Author:** didericis-claude +- **Created:** 2026-07-25 +- **Issue:** #423 + +## Summary + +Move author identity and named forge configurations into host-trusted agent +definitions. A bottle may optionally associate a git-gate repository with one +of the selected agent's forge aliases. When associated, bot-bottle gives the +agent non-secret, provider-specific system guidance for that repository and +routes authenticated forge API calls through the egress proxy without exposing +the forge token to the bottle. The authenticated account is inferred from the +identity that owns that token; it is not separately declared in the manifest. + +Repository-local agent and bottle definitions are no longer discovered. Once +agent definitions can select a host forge credential, allowing checked-out +repository content to define or override an agent would let untrusted workspace +content select host identities and secrets. + +This PRD is deliberately limited to identity ownership, manifest trust, forge +API access, and generated guidance. Per-activation signing, signature +enforcement, commit attribution, and the commit audit model move to a follow-up +PRD. + +Successor to: + +- **PRD 0011 (per-file manifests)** — allowed repository-local agent files to + override home agents. This PRD removes that trust path: agents and bottles are + loaded only from the host-owned `~/.bot-bottle` tree. +- **PRD 0027 (agent git identity, #94)** / **ADR 0002** — put name/email in + `git-gate.user` while keeping them claimed rather than vouched. This PRD moves + those agent properties out of git-gate and into the trusted agent definition. +- **PRD 0048 (deploy-key provisioning, #169)** — remains the Git push + capability. A forge actor token is a separate API credential and never + replaces a deploy key. + +## Problem + +### Forge workflow context is missing + +The agent prompt does not know which forge backs a git-gate repository, which +API base URL to use, or that authenticated requests must go through the egress +proxy. Bespoke prompt text has drifted between agents. Missing guidance has +already caused incorrect PR behavior, including attempts to use Gitea AGit +review refs instead of a normal branch-backed pull request. + +### Identity is owned by the wrong layer + +`git-gate.user` puts an agent property on a repository transport component. An +author identity and set of forge credentials should follow the agent across +bottles and repositories. Git-gate should own Git transport policy, not decide +who the agent is. + +### Repository-local agents become a credential-selection path + +Today `$CWD/.bot-bottle/agents/*.md` can define new agents and override +home-resident agents. If an agent definition may reference an operator-provided +forge token, a malicious repository could select a host credential merely by +being the current workspace. Repository-local bottles are already ignored; +agents need the same host-only boundary. + +## Goals / Success Criteria + +- **Agent-owned identity.** Author name/email and named forge configurations + live on the agent definition, not under `git-gate`. +- **Trusted definitions only.** Agent and bottle files are discovered only + under `~/.bot-bottle/{agents,bottles}`. Repository-local definitions never + contribute names, defaults, or overrides. +- **Optional repository association.** A bottle repository may name one forge + alias from the selected agent. Repositories without `forge` retain current + behavior and generate no forge guidance or credential route. +- **Proxy-held forge credential.** The host resolves the selected forge + alias's token reference and gives the value only to the egress proxy. The + bottle receives neither the token nor a credential file containing it. +- **Forge-aware system guidance.** For associated repositories, bot-bottle + generates provider-specific instructions describing the API base URL, + repository/account association, proxy-authenticated access, and correct PR + workflow. +- **No secret prompt material.** Neither token values nor token + environment-variable names appear in the prompt or bottle environment. +- **Fail closed.** Unknown account references, unsupported/non-HTTPS URLs, + missing host secrets, and misplaced agent/bottle fields fail before creating + the bottle. +- **Push capability unchanged.** Git transport remains PRD 0048 deploy keys. + The forge actor token is only for API actions such as opening, reviewing, and + commenting on pull requests. + +## Non-goals + +- **Commit signing or signature enforcement.** No signing key is minted and + git-gate does not verify commit signatures in this PRD. +- **Commit attribution or audit tables.** There is no trustworthy commit + observation event in this slice. A follow-up signing PRD owns activation keys, + control-plane verification, and any configured-author-versus-claimed-author + audit model. +- **Cryptographically vouched author identity.** `author` configures Git and + identifies the agent actor, but commit author/committer fields remain claims + under ADR 0002. +- **Forge account or token minting.** The operator creates the agent-specific + account/token out of band. Bot-bottle references an existing host secret; it + does not create, rotate, or revoke that credential. +- **Forge-side commit attribution surfaces.** No commit status, signing-key + registration, or "Verified" badge. +- **Provider-generic arbitrary prompts.** Gitea is the first supported forge. + A future provider adds typed validation and generated guidance in code rather + than accepting repository-supplied prompt text. + +## Design + +### Ownership model + +The resolved bottled agent is an agent definition composed with a bottle: + +| Part | Source | Role | +|------|--------|------| +| Author identity | agent `author` | configures Git name/email and identifies the agent's claimed author | +| Forge configurations | agent `forge-accounts` | maps forge aliases to API origins and host token references available to this agent; token ownership determines account identity | +| Git repository | bottle `git-gate.repos` | configures Git transport, host verification, and push capability | +| Repository/forge association | bottle repo `forge` | optionally selects an agent forge alias for guidance and API access | + +This boundary keeps identity on the agent, capability/policy on the bottle, and +transport enforcement in git-gate. + +### Agent manifest + +Agent identity and forge accounts live only in +`~/.bot-bottle/agents/.md`: + +```yaml +--- +author: + name: didericis-claude + email: eric+claude@dideric.is +forge-accounts: + didericis-gitea: + auth: + type: token + token_secret: GITEA_CLAUDE_TOKEN + url: https://gitea.dideric.is/api/v1 +--- +``` + +`author` contains: + +- `name`: non-empty string; +- `email`: non-empty string passing the existing Git identity validation. + +The resolved values populate `user.name` and `user.email`. Existing +`git-gate.user` fields fail with migration guidance to move the values into the +selected home agent's `author` block. There is no compatibility period where a +bottle identity silently overrides the agent identity. + +`forge-accounts` is a map whose keys are **forge aliases** and follow the +manifest's existing kebab-case identifier grammar +(`[a-z][a-z0-9-]*`). Each forge entry contains: + +- `url`: a canonical HTTPS Gitea API base URL; +- `auth.type`: `token` in this slice; +- `auth.token_secret`: the name of a host environment variable containing the + operator-provided, agent-specific API token. + +The token's owner determines the authenticated forge account; there is no +separate account-name field. The token secret name is host configuration, not +bottle configuration. The token value is resolved only if a selected bottle +repository references the forge alias. + +### Bottle manifest + +Repository policy remains in `~/.bot-bottle/bottles/.md`: + +```yaml +--- +git-gate: + repos: + bot-bottle: + url: ssh://git@100.78.141.42:30009/didericis/bot-bottle.git + provisioned_key: + provider: gitea + token_env: GITEA_DEPLOY_TOKEN + host_key: "ssh-ed25519 AAAA..." + forge: didericis-gitea +--- +``` + +`git-gate.repos..forge` is optional: + +- When absent, the repository behaves exactly as it does today. Bot-bottle does + not resolve a forge actor token and does not add forge-specific instructions + for that repository. +- When present, it must match a forge alias in `forge-accounts` on the selected + agent. + The resolved association enables a scoped proxy credential route and adds the + repository/forge relationship to generated system guidance. + +`provisioned_key.token_env` remains the deploy-key administration credential +from PRD 0048. It is separate from `forge-accounts.*.auth.token_secret`: the +former provisions Git push capability, while the latter performs API actions as +the agent. + +`author` and `forge-accounts` are agent-only. `git-gate.repos`, including +`forge`, is bottle-only. Validation errors point to the correct file and trust +domain rather than ignoring misplaced keys. + +### Definition trust and discovery + +Only the host-owned manifest tree is authoritative: + +- Agents: `~/.bot-bottle/agents/*.md` +- Bottles: `~/.bot-bottle/bottles/*.md` + +`$CWD/.bot-bottle/agents/*.md` no longer contributes new agents and no longer +overrides a home agent. `$CWD/.bot-bottle/bottles/*.md` remains unusable. If +either repository-local directory contains manifest files, bot-bottle emits a +warning that they are ignored and points to the corresponding home path. + +Every discovery and resolution surface uses the same home-only index: + +- agent enumeration and selectors; +- `require_agent`; +- lazy `load_for_agent`; +- default-agent/default-bottle resolution; +- dashboard and headless launch paths. + +There must be no alternate direct-path load that can still select a workspace +definition. + +Workspace instructions remain repository content (for example `AGENTS.md`), +but runtime policy, host secret references, and actor identity do not. +Programmatic in-memory manifests remain available for tests and trusted internal +composition; they are not a filesystem discovery path. + +### Forge URL validation + +The host parses and canonicalizes each referenced forge alias's URL before +creating any runtime resources: + +- scheme must be `https`; +- userinfo, query, and fragment are forbidden; +- hostname must be present; +- the path must be a supported Gitea API base (initially `/api/v1`, with + normalization of a trailing slash); +- visually different inputs that canonicalize to the same origin/prefix are + deduplicated; +- unsupported providers or path shapes fail closed. + +The provider is determined by typed support in bot-bottle, not by prompt text +from a repository. Adding another provider requires a validator, auth scheme, +and guidance renderer. + +### Proxy credential provisioning + +For each distinct forge alias referenced by at least one selected bottle +repository, the host: + +1. Resolves `auth.token_secret` from the host environment and rejects a missing + or empty value. +2. Copies the token value only into the egress proxy's credential environment. +3. Adds an inspected route scoped to the canonical forge origin and API prefix. +4. Configures the provider authentication scheme (`token` for Gitea) so the + proxy injects authentication. + +The bottle makes an unauthenticated request to the configured HTTPS API URL. +The token is not copied into the bottle, `.gitconfig`, generated prompt, +workspace, or process environment visible to the agent. + +If several repositories reference the same forge alias, they share one +credential route. Unreferenced forge aliases resolve no secret and create no +route. + +### Generated system guidance + +Bot-bottle appends a generated, non-secret section to its existing system +prompt file. It is derived from validated typed fields, not copied Markdown from +a repository. + +For each associated repository, Gitea guidance includes: + +- forge alias (for example `didericis-gitea`); +- API base URL (for example `https://gitea.dideric.is/api/v1`); +- the git-gate repository name tied to that forge; +- the instruction to call the configured HTTPS API through the proxy without + reading, printing, or manually attaching an authorization token; +- the distinction that Git pushes still use the git-gate remote; +- the requirement to create/update a normal `refs/heads/` and open a + branch-backed pull request through the API; +- the prohibition on pushing `refs/for/*`, `refs/draft/*`, or + `refs/for-review/*`; +- the instruction to use the API for reviews/comments and verify returned + object state before claiming completion. + +The prompt contains neither the token value nor its `token_secret` name. +Repositories without `forge` are omitted from this section. If no selected +repository has a forge association, no forge guidance section is generated. + +### Failure and lifecycle behavior + +Forge configuration is validated before bottle creation. A bad association or +credential must not leave a partially-created bottle or proxy. + +The operator-owned token is not minted, rotated, or revoked by bot-bottle. On +teardown, stopping the egress proxy discards the activation's in-memory/runtime +copy. Later activations resolve the current host secret again. + +Logs may include the forge alias, canonical API origin, and repository name. +They must never contain the token value. Errors for missing secrets name the +configuration field and host environment variable, but do not print any value. + +## Migration + +This change is intentionally breaking at the manifest trust boundary: + +1. Move each home bottle/agent `git-gate.user.name` and `.email` into the + corresponding home agent's `author`. +2. Add agent-specific `forge-accounts` only to home agent definitions. +3. Add optional `forge` associations to home bottle repository entries. +4. Move any `$CWD/.bot-bottle/agents/*.md` that should remain selectable into + `~/.bot-bottle/agents/`. Repository copies are ignored thereafter. +5. Keep repository-specific behavioral instructions in `AGENTS.md` or another + workspace instruction file; do not put runtime identity or secret references + there. + +Errors and warnings link to this migration rather than silently changing which +identity or definition is active. + +## Follow-up: signed commits and attribution + +A separate PRD will consume the trusted agent `author` introduced here and own: + +- per-activation signing key minting and sidecar isolation; +- commit-time signing through a forwarded signing capability; +- git-gate rejection of unsigned/wrong-key new commits; +- independent control-plane object-ID recomputation and signature verification; +- activation and attributed-commit audit tables; +- storage of configured agent author separately from each commit's unenforced + claimed author/committer; +- post-teardown verification and key-retention policy. + +That PRD must not reintroduce identity under git-gate or expand repository-local +manifest trust. + +## Implementation chunks + +1. **This PRD.** Establish the identity, forge, and filesystem trust boundary. +2. **Home-only definitions.** Remove cwd agents from discovery, override, + enumeration, lazy loading, defaults, and selectors. Warn on ignored cwd + agent/bottle files and provide migration guidance. +3. **Manifest schema.** Add agent-only `author` and `forge-accounts`; remove + `git-gate.user`; add optional bottle-only + `git-gate.repos..forge`. Validate account names, composition + references, field placement, and Gitea API URLs. +4. **Proxy provisioning.** Lazily resolve only referenced token secrets and + create scoped authenticated Gitea API routes without putting credentials in + the bottle. +5. **Prompt generation.** Render typed Gitea/repository workflow guidance into + the existing bot-bottle prompt path for associated repositories only. +6. **Docs and migration.** Update README examples, PRD 0011-facing discovery + documentation, agent/bottle schema docs, and error guidance. + +## Testing strategy + +- **Trust boundary:** cwd agent files are ignored with a warning, cannot + override a home agent, are absent from enumeration/selectors/defaults, and + cannot be loaded by name. Cwd bottle behavior remains home-only. +- **Agent schema:** `author` and `forge-accounts` parse; malformed identities, + non-kebab account names, unknown fields, invalid auth types, and misplaced + git-gate fields fail clearly. +- **Bottle schema:** `forge` is optional; absent associations preserve current + behavior; present associations resolve against the selected agent; unknown or + misplaced associations fail before launch. +- **URL validation:** HTTPS Gitea API bases pass and canonicalize; HTTP, + userinfo, query, fragment, missing host, unsupported paths, and unsupported + providers fail closed. +- **Secret handling:** only referenced accounts resolve environment secrets; + missing/empty secrets fail before runtime creation; token values are absent + from bottle env, prompt, generated config, logs, and workspace; token secret + names are absent from the bottle and prompt. +- **Proxy behavior:** associated API requests receive proxy-injected Gitea + authentication scoped to the configured origin/prefix; unrelated hosts and + paths receive no credential. +- **Prompt behavior:** guidance names account/API/repository associations, + branch-backed PR workflow, prohibited AGit refs, and mutation verification; + repositories without `forge` are omitted; no associations means no section. +- **Migration:** legacy `git-gate.user` fails with an `author` migration pointer; + ignored cwd definitions warn with the target home path. + +## Open questions + +- None.