HIPsHanzo Proposals
Back to HIPs
HIP-1232FinalStandards TrackInfrastructure

Git — Repository Hosting

Hanzo AI
Created: 2026-08-20
Requires: HIP-0026, HIP-0106, HIP-0139

HIP-1232: Git — Repository Hosting

Abstract

git is Git hosting for an org: create repos, clone, push, browse, and see what they cost. It mounts the /v1/git surface of the cloud binary — bare repositories on disk, the smart-HTTP and SSH transports git clients speak natively, imports, pulls, mirroring and the browse pages (apps/git/git.go:1-45). It is implemented in hanzoai/cloud at apps/git. This HIP states the target surface: everything under /v1/git, with the host-gated root spellings leaving the binary for the standalone forge that serves that host in production.

Motivation

The capability claimed four top-level prefixes for one product: its own /v1/git, an un-host-gated browse tree at /git, and root routes — /, /explore, /:org/:repo — answered only when the request's Host is the dedicated git host. That host is served in production by the standalone forge, a separate process, so the root claims published addresses this binary never deliverably answers; the manifest walk already refuses a parameter-first prefix.

Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.

The stores

The capability owns per-org stores, not one shared file: each org's repo metadata is its own git.db opened through the org-store seam (apps/git/git.go:222), beside the repositories themselves — bare git repos under {DataDir}/git, with an S3-backed storage seam (apps/git/storage.go) — and one system ssh_keys registry (apps/git/keystore.go:68), global because an SSH connection presents a key before any org is known, so auth is a single fingerprint lookup.

The addresses

Every route is under /v1/git: repos CRUD and usage; the smart-HTTP protocol at /v1/git/:org/:repo/... and, for project-scoped repos, /v1/git/:org/:project/:repo/... — the path is the only channel a git client can carry a project in, since it sends no headers; and the browse pages, folded from /git to /v1/git/explore and /v1/git/:org/:repo{,/tree,/blob,/commits}, where literal segments outrank the :org parameter so nothing collides. The router today still carries the /git tree and the three host-gated root spellings; each pair is a line in cloud's openapi/misfiled.txt. The browse tree closes by fold; the root spellings close by deletion — git.hanzo.ai is served in production by the standalone forge, so removing them changes nothing live and only retires the single-binary dev forge origin, whose clone address remains /v1/git/:org/:repo.

Repos, usage, pulls and control operations are typed. The smart-HTTP operations are declared prose, and cannot be otherwise: their wire is git's own protocol — pkt-line framing, side-band multiplexing, gzip request bodies — not JSON, and the document says so where it matters, telling the consumer this is not an API call to make by hand (apps/git/smart_http.go:38-45). The browse pages answer server-rendered HTML and are declared the same way.

Tenancy

The org is the gateway-minted, IAM-validated claim (HIP-0026), with an optional project sub-scope; every query is scoped by it, so one org can never read, clone, push to or delete another's repos. On the pack routes the path also names an org, and it MUST equal the authenticated one (apps/git/smart_http.go:284). A push is never anonymous. The one read concession is a repository marked public: fetch-side operations answer without an authenticated org, push-side never (apps/git/smart_http.go:292). Over SSH, the presented public key's fingerprint resolves through the global registry to the org whose scope the pack machinery then runs under — the same boundary the HTTP path derives from the header (apps/git/ssh.go:27-33).

Free, in those words

The capability is free: the plugin declares Price: cloud.Free (plugin/git/main.go:21), and no handler gates or meters spend. Usage is measured, not charged: every repo's size is re-measured on create and after each push, exposed per-repo and in total at /v1/git/usage, and each measurement emits a git.usage log line a metering consumer can bill on (apps/git/git.go:696).

Events, observability, stage

The capability publishes no events on the bus, so a customer's webhooks receive nothing from it. A landed receive-pack emits push.landed on the in-process lifecycle stream, and the notification reactor delivers push.landed, deploy.live and deploy.failed to the channels an org has subscribed, durably, deduplicated per event (apps/git/notify.go:48-88). Beyond the request span it emits the git.usage line and structured logs only. Its stage is ga: the manifest row carries no stage field, and absent means ga (HIP-0139 §8).

The push-to-build endpoint is not here. The deploy trigger's one registrant is the platform process, so the forge delivers pushes to platform's hook (HIP-1230); a receiver in this process signed, accepted, answered 204 and built nothing, which is why the address moved.

Upstreams

  • go-git/go-git v5 (Apache-2.0) — repository init and the read path, confined to one file so nothing go-git-shaped escapes it (apps/git/gitbackend.go:3-12).
  • go-git/go-billy v5 (Apache-2.0) — the filesystem abstraction under it.
  • golang.org/x/crypto/ssh (BSD-3-Clause) — the SSH transport.
  • the system git binary (GPL-2.0) — executed as a subprocess for stateless-RPC pack streaming (apps/git/gitexec.go), so multi-GB packs stream through stdin/stdout and never land in this process's memory; invoked, never linked.
  • the smart-HTTP framing patterns are ported from the upstream forge's routers/web/repo/githttp.go (MIT) (apps/git/smart_http.go:30).

Rationale

The alternative to deleting the root spellings is keeping a second clone address alive in a binary that does not serve the host it is gated on — an address the manifest walk refuses and production traffic never reaches. The alternative to declaring the pack operations is typing them, which would publish JSON schemas for a wire that is not JSON and hand every generated SDK twelve methods that corrupt a clone when called.

Security Considerations

The wrong implementation leaks source code. A pack route that trusts the path org instead of comparing it to the authenticated one serves another tenant's repository to anyone who can spell its name; the path-vs-identity guard is the boundary. The SSH key registry is the authentication for the SSH transport, so writing to it is minting access: key registration is org-scoped and the fingerprint lookup is global only because it must run before identity exists. Pack streaming through the CLI is also the memory bound — buffering a push in-process would let one crafted pack exhaust the binary serving every org.

References

  • HIP-0026 — Identity and Access Management
  • HIP-0106 — The Hanzo Plugin Contract
  • HIP-0139 — Capability
  • HIP-1230 — Platform — The Container Plane

Copyright

Released under CC0 1.0 Universal Public Domain Dedication.