6.3 KiB
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.emailidentity, 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-gateis accepted as a top-level bottle and agent key;gitis removed from both allowed-key sets.git-gate.reposis 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.reposaccepts exactly:url(required),identity(required),host_key(optional). git-gate.userreplacesgit.useron both bottles and agents, with the samename/emailfields and overlay semantics.- The manifest parser rejects
git.remotesandgit.userwith errors that point to the new keys. GitEntryinternal 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.pyandtests/unit/test_manifest_git_user.pyare 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.usersemantics or field names (name,email). - No change to git-gate runtime behavior (mirroring, gitleaks, access-hook refresh).
- No change to the
insteadOfrewrite 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):
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:
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"inBOTTLE_KEYSandAGENT_KEYS_OPTIONAL. -
manifest.py: replace_parse_git_configwith_parse_git_gate_configthat validates bothuserandrepossubkeys. UpdateBottle.from_dictandAgent.from_dictto call it for the"git-gate"key. -
Agent.from_dictcontinues to rejectreposat the agent level with a clear error. -
Remove
from_remote_dictand updateGitEntry._from_objectto 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 usegit-gate.repos/ lowercase fields. Cover: minimal entry, optionalhost_key, missingurl, missingidentity, unknown key, IP-literal upstreams, duplicate name rejection, oldgit.remotesand baregitkey both rejected.tests/unit/test_manifest_git_user.pyandtests/unit/test_manifest_agent_git_user.py— update fixtures to usegit-gate.userat both bottle and agent level.
Open Questions
None.