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.
This commit is contained in:
2026-06-03 03:38:33 +00:00
committed by didericis
parent 59fd132b9d
commit 64ac204c05
+48 -46
View File
@@ -7,19 +7,20 @@
## Summary ## Summary
Replace the `git.remotes` subsection in bottle manifests with a top-level Replace the `git` top-level key in bottle and agent manifests with `git-gate`,
`git-gate` key whose `repos` map uses lowercase snake_case field names and consolidating git-identity configuration (`user`) and git-gate sidecar
derives the local repo name from the YAML key. The change removes the configuration (`repos`) under a single section. Within `repos`, field names
ambiguity that the current `git` block carries: its fields are not generic git move to lowercase snake_case and the local repo name is promoted to the YAML
or SSH config — they are specifically the credential and host-trust material key. The change removes the ambiguity in the current `git` block: its fields
the git-gate sidecar needs to mirror each upstream. 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 ## Problem
The current bottle manifest uses a `git` top-level key that mixes two concerns: 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 - `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.remotes` — upstream URL, identity file, and host key material that the
git-gate sidecar consumes; the agent never sees these values. 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 `.gitconfig` receives only the `insteadOf` rewrite that redirects traffic
through the gate. Nothing in the current key name or field names signals this. 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`, The field names inside each remote entry also use PascalCase (`Name`,
`Upstream`, `IdentityFile`, `KnownHostKey`), inconsistent with every other `Upstream`, `IdentityFile`, `KnownHostKey`), inconsistent with every other
manifest section, which uses snake_case. 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 ## Goals / Success Criteria
- `git-gate` is accepted as a top-level bottle key; `git-gate.repos` is a - `git-gate` is accepted as a top-level bottle and agent key; `git` is removed
named map where each key is the local repo name exposed by the gate. 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` - Each entry in `git-gate.repos` accepts exactly: `url` (required), `identity`
(required), `host_key` (optional). (required), `host_key` (optional).
- The `git.remotes` subkey is removed from the `git` block; `git` accepts only - `git-gate.user` replaces `git.user` on both bottles and agents, with the
`user` (unchanged). same `name` / `email` fields and overlay semantics.
- The manifest parser rejects `git.remotes` with an error that points to the - The manifest parser rejects `git.remotes` and `git.user` with errors that
new key. point to the new keys.
- `GitEntry` internal fields are updated to match the new names; all callers - `GitEntry` internal fields are updated to match the new names; all callers
(provisioner, git-gate render, plan, tests) compile and pass. (provisioner, git-gate render, plan, tests) compile and pass.
- Existing unit tests in `tests/unit/test_manifest_git.py` are rewritten to - Existing unit tests in `tests/unit/test_manifest_git.py` and
use the new YAML shape; all other manifest unit tests remain green. `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 - The demo manifest (`bot-bottle.demo.json`) and any examples using the old
shape are updated. shape are updated.
## Non-goals ## 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 - No change to git-gate runtime behavior (mirroring, gitleaks, access-hook
refresh). refresh).
- No change to the `insteadOf` rewrite the provisioner emits. - No change to the `insteadOf` rewrite the provisioner emits.
- No migration shim: the old `git.remotes` shape is rejected immediately with - No migration shim: the old `git.*` shape is rejected immediately with clear
a clear error message. error messages pointing to the new keys.
- No change to how agent-level `git.user` overlays the bottle-level value. - No change to how agent-level user config overlays the bottle-level value.
## Design ## Design
@@ -87,12 +96,10 @@ git:
**After**: **After**:
```yaml ```yaml
git: git-gate:
user: user:
name: implementer-bot name: implementer-bot
email: eric+implementer@dideric.is email: eric+implementer@dideric.is
git-gate:
repos: repos:
bot-bottle: bot-bottle:
url: ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git url: ssh://git@gitea.dideric.is:30009/didericis/bot-bottle.git
@@ -100,9 +107,9 @@ git-gate:
host_key: "ssh-rsa AAAA..." host_key: "ssh-rsa AAAA..."
``` ```
The `git` block is unchanged and remains optional; `git-gate` is a separate `git-gate` is the single optional top-level key for all git configuration.
optional top-level key. Bottles that use only `git.user` continue to work Bottles that previously used only `git.user` now use only `git-gate.user`;
without touching `git-gate`. those that used only `git.remotes` now use only `git-gate.repos`.
### Key-name-as-repo-name ### Key-name-as-repo-name
@@ -122,26 +129,23 @@ without an artificial host-as-key constraint.
### Parser changes ### Parser changes
- `manifest_schema.py`: add `"git-gate"` to `BOTTLE_KEYS`; leave `"git"` in - `manifest_schema.py`: replace `"git"` with `"git-gate"` in `BOTTLE_KEYS`
`BOTTLE_KEYS` (it still carries `user`). and `AGENT_KEYS_OPTIONAL`.
- `manifest.py`: add `_parse_git_gate_config(bottle_name, raw)` that validates - `manifest.py`: replace `_parse_git_config` with `_parse_git_gate_config`
the new shape and returns `tuple[GitEntry, ...]`. Update `Bottle.from_dict` that validates both `user` and `repos` subkeys. Update `Bottle.from_dict`
to call it for the `"git-gate"` key. 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 - Remove `from_remote_dict` and update `GitEntry._from_object` to accept the
new field names. Internal dataclass field names (`UpstreamUser`, etc.) are new field names. Internal dataclass field names (`UpstreamUser`, etc.) are
unchanged — they are internal plumbing, not user-facing. 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 bottle 'dev' uses 'git' which has been replaced by 'git-gate' (PRD 0047).
under 'git-gate.repos' instead (see 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 ## Testing Strategy
Run: Run:
@@ -155,14 +159,12 @@ Test files to update:
- `tests/unit/test_manifest_git.py` — rewrite fixtures and assertions to use - `tests/unit/test_manifest_git.py` — rewrite fixtures and assertions to use
`git-gate.repos` / lowercase fields. Cover: minimal entry, optional `git-gate.repos` / lowercase fields. Cover: minimal entry, optional
`host_key`, missing `url`, missing `identity`, unknown key, IP-literal `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 ## Open Questions
- **`git.user` on agents after `git` narrows.** Today both bottle and agent None.
`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).