The gate is now a transparent mirror, not push-only. Per-repo
init now runs `git remote add --mirror=fetch origin <url>` so a
later `git fetch origin` mirrors the upstream's full ref graph at
canonical paths. The pre-receive hook forwards accepted refs via
`git push origin` (renamed from upstream).
New: an access-hook script wired via `git daemon --access-hook`
runs `git fetch origin --prune` against the real upstream before
every upload-pack request (clone, fetch, pull, ls-remote). On
upstream error the hook exits non-zero — the agent's fetch fails
rather than the gate serving stale data.
The pre-existing smoke test (ls-remote against unreachable
upstream returns refs) had to invert: under the bidirectional
design any ls-remote success is necessarily a success against
the upstream, so the unreachable-upstream case now correctly
fails closed.
Two integration tests against a real Docker daemon:
- test_ls_remote_succeeds_against_fresh_gate: a freshly-started
gate has its empty bare repo exported via git daemon; ls-remote
from a sibling container on the internal network returns no
refs and exits 0.
- test_push_with_secret_is_rejected: the PRD 0008 success
criterion — a push containing an AKIA-shaped synthetic that
trips gitleaks's aws-access-token rule is rejected by the
pre-receive hook with a non-zero exit on the client and a
gitleaks rejection in the response.
Dockerfile.git-gate switches base to zricethezav/gitleaks (alpine
3.22 + gitleaks v8.30.1, pinned by digest) since gitleaks isn't
packaged for alpine, and adds git-daemon (the sub-package the
listener needs; the core git binary in the base doesn't include
the daemon).