From 02d61e337f0e3108bb01b15c94072b0ce4064efc Mon Sep 17 00:00:00 2001 From: claude Date: Wed, 3 Jun 2026 03:38:33 +0000 Subject: [PATCH] docs(prd): consolidate git.user into git-gate per review Move git.user under git-gate and remove git as a top-level key entirely, so all git configuration lives under a single section. --- docs/prds/0047-git-gate-manifest-redesign.md | 94 ++++++++++---------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/docs/prds/0047-git-gate-manifest-redesign.md b/docs/prds/0047-git-gate-manifest-redesign.md index 3fdd8e0..4e907a8 100644 --- a/docs/prds/0047-git-gate-manifest-redesign.md +++ b/docs/prds/0047-git-gate-manifest-redesign.md @@ -7,19 +7,20 @@ ## Summary -Replace the `git.remotes` subsection in bottle manifests with a top-level -`git-gate` key whose `repos` map uses lowercase snake_case field names and -derives the local repo name from the YAML key. The change removes the -ambiguity that the current `git` block carries: its fields are not generic git -or SSH config — they are specifically the credential and host-trust material -the git-gate sidecar needs to mirror each upstream. +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 and is not gate-specific. + 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. @@ -29,6 +30,10 @@ 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. @@ -40,30 +45,34 @@ a local label. The host key and `Name` field are often redundant or confusing ## Goals / Success Criteria -- `git-gate` is accepted as a top-level bottle key; `git-gate.repos` is a - named map where each key is the local repo name exposed by the gate. +- `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). -- The `git.remotes` subkey is removed from the `git` block; `git` accepts only - `user` (unchanged). -- The manifest parser rejects `git.remotes` with an error that points to the - new key. +- `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` are rewritten to - use the new YAML shape; all other manifest unit tests remain green. +- 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` semantics or field names. +- 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.remotes` shape is rejected immediately with - a clear error message. -- No change to how agent-level `git.user` overlays the bottle-level value. +- 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 @@ -87,12 +96,10 @@ git: **After**: ```yaml -git: +git-gate: user: name: implementer-bot email: eric+implementer@dideric.is - -git-gate: repos: bot-bottle: url: ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git @@ -100,9 +107,9 @@ git-gate: host_key: "ssh-rsa AAAA..." ``` -The `git` block is unchanged and remains optional; `git-gate` is a separate -optional top-level key. Bottles that use only `git.user` continue to work -without touching `git-gate`. +`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 @@ -122,26 +129,23 @@ without an artificial host-as-key constraint. ### Parser changes -- `manifest_schema.py`: add `"git-gate"` to `BOTTLE_KEYS`; leave `"git"` in - `BOTTLE_KEYS` (it still carries `user`). -- `manifest.py`: add `_parse_git_gate_config(bottle_name, raw)` that validates - the new shape and returns `tuple[GitEntry, ...]`. Update `Bottle.from_dict` - to call it for the `"git-gate"` key. +- `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. -- `_parse_git_config` narrows to reject `remotes` with a helpful error: +- Any existing `"git"` key raises a targeted error: ``` - bottle 'dev' git.remotes is no longer supported; declare git-gate upstreams - under 'git-gate.repos' instead (see PRD 0047). + 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. ``` -### Error on rejected key - -The parser emits the error above whenever `git.remotes` is present, regardless -of whether `git-gate` is also present. - ## Testing Strategy Run: @@ -155,14 +159,12 @@ 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` shape rejected. + 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 -- **`git.user` on agents after `git` narrows.** Today both bottle and agent - `git` blocks are validated by the same `_parse_git_config` path. After this - change, bottle `git` allows only `user`; agent `git` already only allows - `user`. No code change needed — but confirm the agent-side rejection message - for `git.remotes` still makes sense once `remotes` is also invalid for - bottles (the current message says "remotes is bottle-only"; after this PRD - it's invalid everywhere). +None.