Files
bot-bottle/docs/prds/0047-git-gate-manifest-redesign.md
T
didericis-claude 59fd132b9d docs(prd): add git-gate manifest redesign plan
PRD 0047 proposes replacing git.remotes with a top-level git-gate.repos
section and snake_case field names to make clear the config is
specifically for git-gate routing, not generic git or SSH config.

Closes #160
2026-06-02 23:59:34 -04:00

6.1 KiB

PRD 0047: Git-gate Manifest Redesign

  • Status: Draft
  • Author: didericis
  • Created: 2026-06-03
  • Issue: #160

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.

Problem

The current bottle manifest uses a git top-level key that mixes two concerns:

  • git.usergit config --global user.name / user.email identity, which the provisioner injects into the agent's shell and is not gate-specific.
  • 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.

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 key; git-gate.repos is a named map where each key is the local repo name exposed by the gate.
  • 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.
  • 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.
  • 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-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.

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:
  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
      identity: ~/.ssh/gitea-delos-2.pem
      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.

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: 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.

  • 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:

    bottle 'dev' git.remotes is no longer supported; declare git-gate upstreams
    under 'git-gate.repos' instead (see PRD 0047).
    

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:

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 shape rejected.

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).