# PRD 0047: Git-gate Manifest Redesign - **Status:** Active - **Author:** didericis - **Created:** 2026-06-03 - **Issue:** #160 ## Summary Replace the `git` top-level key in bottle and agent manifests with `git-gate`, consolidating git-identity configuration (`user`) and git-gate sidecar configuration (`repos`) under a single section. Within `repos`, field names move to lowercase snake_case and the local repo name is promoted to the YAML key. The change removes the ambiguity in the current `git` block: its fields are not generic git or SSH config — they are specifically the credential, host-trust, and identity material that is managed in relation to git-gate. ## Problem The current bottle manifest uses a `git` top-level key that mixes two concerns: - `git.user` — `git config --global user.name / user.email` identity, which the provisioner injects into the agent's shell. - `git.remotes` — upstream URL, identity file, and host key material that the git-gate sidecar consumes; the agent never sees these values. That grouping suggests the `remotes` entries behave like an SSH config or a generic `.gitconfig` remote declaration. They do not. The gate reads the credential material to push upstream after gitleaks passes; the agent's `.gitconfig` receives only the `insteadOf` rewrite that redirects traffic through the gate. Nothing in the current key name or field names signals this. Splitting `git.user` into a separate section from `git.remotes` also doesn't help: both concepts exist because of git-gate, and keeping them under a single `git-gate` key makes their relationship and purpose explicit. The field names inside each remote entry also use PascalCase (`Name`, `Upstream`, `IdentityFile`, `KnownHostKey`), inconsistent with every other manifest section, which uses snake_case. The current `git.remotes` dict is keyed by upstream host, which works for simple remotes but forces a separate `Name` field to give the gate's bare repo a local label. The host key and `Name` field are often redundant or confusing (e.g., IP-literal upstreams where the key carries no semantic meaning). ## Goals / Success Criteria - `git-gate` is accepted as a top-level bottle and agent key; `git` is removed from both allowed-key sets. - `git-gate.repos` is a named map where each key is the local repo name exposed by the gate (bottle-only; rejected at the agent level). - Each entry in `git-gate.repos` accepts exactly: `url` (required), `identity` (required), `host_key` (optional). - `git-gate.user` replaces `git.user` on both bottles and agents, with the same `name` / `email` fields and overlay semantics. - The manifest parser rejects `git.remotes` and `git.user` with errors that point to the new keys. - `GitEntry` internal fields are updated to match the new names; all callers (provisioner, git-gate render, plan, tests) compile and pass. - Existing unit tests in `tests/unit/test_manifest_git.py` and `tests/unit/test_manifest_git_user.py` are rewritten to use the new YAML shape; all other manifest unit tests remain green. - The demo manifest (`bot-bottle.demo.json`) and any examples using the old shape are updated. ## Non-goals - No change to `git.user` / `git-gate.user` semantics or field names (`name`, `email`). - No change to git-gate runtime behavior (mirroring, gitleaks, access-hook refresh). - No change to the `insteadOf` rewrite the provisioner emits. - No migration shim: the old `git.*` shape is rejected immediately with clear error messages pointing to the new keys. - No change to how agent-level user config overlays the bottle-level value. ## Design ### New manifest shape **Before** (bottle frontmatter): ```yaml git: user: name: implementer-bot email: eric+implementer@dideric.is remotes: gitea.dideric.is: Name: bot-bottle Upstream: ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git IdentityFile: ~/.ssh/gitea-delos-2.pem KnownHostKey: "ssh-rsa AAAA..." ``` **After**: ```yaml git-gate: user: name: implementer-bot email: eric+implementer@dideric.is repos: bot-bottle: url: ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git identity: ~/.ssh/gitea-delos-2.pem host_key: "ssh-rsa AAAA..." ``` `git-gate` is the single optional top-level key for all git configuration. Bottles that previously used only `git.user` now use only `git-gate.user`; those that used only `git.remotes` now use only `git-gate.repos`. ### Key-name-as-repo-name The YAML key in `git-gate.repos` becomes the local repo name (previously `Name`). The upstream host is no longer the primary key; the provisioner and gate derive it from the `url` field during parse. IP-literal upstreams work without an artificial host-as-key constraint. ### Field renames | Old field | New field | |-----------|-----------| | `Name` (from dict key) | YAML key in `repos` | | `Upstream` | `url` | | `IdentityFile` | `identity` | | `KnownHostKey` | `host_key` | ### Parser changes - `manifest_schema.py`: replace `"git"` with `"git-gate"` in `BOTTLE_KEYS` and `AGENT_KEYS_OPTIONAL`. - `manifest.py`: replace `_parse_git_config` with `_parse_git_gate_config` that validates both `user` and `repos` subkeys. Update `Bottle.from_dict` and `Agent.from_dict` to call it for the `"git-gate"` key. - `Agent.from_dict` continues to reject `repos` at the agent level with a clear error. - Remove `from_remote_dict` and update `GitEntry._from_object` to accept the new field names. Internal dataclass field names (`UpstreamUser`, etc.) are unchanged — they are internal plumbing, not user-facing. - Any existing `"git"` key raises a targeted error: ``` bottle 'dev' uses 'git' which has been replaced by 'git-gate' (PRD 0047). Move git.user → git-gate.user and git.remotes → git-gate.repos. ``` ## Testing Strategy Run: ``` python3 -m unittest discover -s tests/unit ``` Test files to update: - `tests/unit/test_manifest_git.py` — rewrite fixtures and assertions to use `git-gate.repos` / lowercase fields. Cover: minimal entry, optional `host_key`, missing `url`, missing `identity`, unknown key, IP-literal upstreams, duplicate name rejection, old `git.remotes` and bare `git` key both rejected. - `tests/unit/test_manifest_git_user.py` and `tests/unit/test_manifest_agent_git_user.py` — update fixtures to use `git-gate.user` at both bottle and agent level. ## Open Questions None.