clone

nostr relays on demand at <name>.bind.ws: claim one with your key, pay in zaps past the free allowance

dependabot/npm_and_yarn/npm-f01a459b0f
bindws / docs / 22-grasp-01-git-hosting.md
22-grasp-01-git-hosting.md 16.8K rendered
1---2title: GRASP-01 Git hosting3audience: user4---56# GRASP-01 Git hosting78GRASP-01 gives a relay an optional Git Smart HTTP door. A repository owner9publishes the NIP-34 repository announcement and its current state on the10relay; the relay serves the Git objects that belong to that accepted state.11The Nostr relay remains the source of authorization and discussion events.1213This document tracks the GRASP-01 specification at [commit14f35b4f9a4ed2f0aaaf46926e4b1733c79e21b377](https://github.com/DanConwayDev/grasp/tree/f35b4f9a4ed2f0aaaf46926e4b1733c79e21b377),15checked on 2026-09-04. The implementation is bounded to the core service. It16The later extensions are independently opt-in: `grasp02`, `grasp03`,17`grasp05` and `grasp06`. GRASP-02 and GRASP-06 require `grasp`; GRASP-03 and18GRASP-05 require GRASP-02. These switches default off, so enabling `grasp`19alone preserves GRASP-01 behavior. This document describes the host20implementation and does not claim an upstream audit or complete conformance.2122## Turn it on2324The `grasp` feature is off unless the owner enables it. The **Git repositories** preset25turns on open reads and the GRASP Git door. The later flags are separately26opt-in and the dependency rules above apply. The repository still applies its27ordinary fuel, storage and event rules, and the information document lists28the exact acceptance criteria. The relay advertises `GRASP-01` and, when29validated and enabled, `GRASP-06` in `supported_grasps`. GRASP-02/03/05 remain30bounded opt-in previews pending interoperability and conformance review.3132The [relay templates](../relay-templates/README.md) include a GRASP template33for a relay whose public purpose is hosting NIP-34 repositories. A template34does not add members, bans or addresses. The owner supplies those separately35when the relay's event policy requires them.3637## Announce a repository3839The repository owner publishes a kind 30617 event with a `d` identifier,40`clone` URLs and `relays` URLs. An announcement that hosts a repository41here must list this service in both lists. A companion announcement42from an already recognized maintainer may omit those service tags because the43repository is already hosted here. The service URL is the relay's HTTPS Git44endpoint and its WebSocket relay URL. The identifier is percent-encoded in the45Git path:4647```48/<npub>/<percent-encoded-identifier>.git49```5051The relay accepts the announcement only after its normal signature, ban,52fuel, size and event policy checks. A malformed identifier, URL or maintainer53key is refused. A repository's `private` tag does not make a private Git door;54private repository hosting is GRASP-08 and remains outside this feature.5556The accepted announcement's address is57`30617:<owner-pubkey>:<identifier>`. NIP-34 issues, patches, pull requests and58updates that refer to an accepted address can be stored by the normal relay59rules. GRASP-06 pull requests may arrive before the announcement: they must60still name a syntactically valid repository coordinate and remain in purgatory61until their matching Git data arrives.6263## Publish a state6465The repository maintainer publishes a kind 30618 event with the same `d`66identifier, a `HEAD` tag and the complete `refs/heads/*` and `refs/tags/*` map.67The [NIP-34 HEAD tag](https://github.com/nostr-protocol/nips/blob/master/34.md#repository-state-announcements)68uses `["HEAD", "ref: refs/heads/main"]`; a bare branch path is refused.69The latest valid state is selected by NIP-34 addressable-event ordering among70the owner and the recursive maintainer set. The recursive set starts at the71owner and follows the maintainer announcements for that repository, with72bounded depth and membership limits.7374The Git door accepts a normal branch or tag update only when each changed ref75and the requested HEAD agree with the accepted state. Unchanged refs remain76untouched, so a push does not need to repeat the entire state map. The state77event therefore authorizes the bytes a push makes visible; a maintainer78signature alone does not authorize an arbitrary branch. A state with no HEAD79represents an unborn repository. A pending state keeps the previously80materialized branch and HEAD in the Git repository, so a stock clone still81checks out the prior branch. The repository moves to the new HEAD when the82new tip's objects arrive.8384## Pull requests and purgatory8586The alternative PR endpoint is `/prs/<signer-npub>/<percent-encoded-identifier>.git`.87It is a public, empty repository namespace with at most 16 hosted namespaces88and 1,024 refs per namespace. Only `refs/nostr/<event-id>` refs are accepted;89ordinary branches, tags and client deletions are refused. A pre-announcement90PR must carry the exact signer PR clone URL and a valid repository coordinate91with the same identifier. The signed PR or update and its `c` tip authorize the92ref. Unknown events may upload first, but remain hidden until the event is93accepted; their deadline is fixed at 20 minutes. Expiry removes refs, while94immutable objects and orphaned chunks remain retained and charged because95the service has no garbage collector.9697Promotion checks for relevant pending events before loading Git packs. An98empty queue or another repository's pending work does not trigger that read.99A retried Git write still completes any pending promotion left by an earlier100successful publication; expiry and authority are checked again after reads.101102A pull request or update can name a Git ref as103`refs/nostr/<event-id>`. The event ID is checked as a 64-character lowercase104hex value. The relay can hold the event and the Git data while it waits for105the other half of the pair. Until both are present, the event is not served106from the normal read path and the response explains107`purgatory: won't be served until git data arrives`. Unresolved entries expire108after 30 minutes.109110The relay accepts every syntactically valid `refs/nostr/<event-id>` push,111including one whose Nostr event has not arrived yet. It rejects a ref whose112known event names a different tip. A pending PR ref that has no accepted PR113or PR update with a matching `c` tag is hidden and eligible for cleanup after11420 minutes. The cleanup window limits abandoned uploads and does not implement115the broader archive or sync behavior of later GRASP documents.116117## Git HTTP118119Your relay's **Connect** section includes a copyable `git clone` example with120its own hostname, the repository owner's npub and repository name.121**Open in app** opens this relay's repositories in122[GitWorkshop](https://gitworkshop.dev/), alongside a copyable relay URL.123Both Git connections stay hidden when Git hosting is unavailable.124125The Git door is unauthenticated when the relay's reads are open. It serves126Git Smart HTTP at the repository path and returns a repository page or a 404127for a path that is not hosted. Upload-pack advertises and serves128`allow-reachable-sha1-in-want`, `allow-tip-sha1-in-want` and `filter`, including129`blob:none` and `tree:0` requests. Receive-pack applies the accepted state,130maintainer and `refs/nostr` rules before storing anything.131132The relay root, Git repository page and protocol responses include the GRASP133CORS headers. `OPTIONS` returns 204. GET and POST are the allowed methods;134`Content-Type`, `Authorization`, `Git-Protocol` and `X-Git-Request-Id` are135allowed request headers. The NIP-5A site origins remain isolated from relay136events and repository data.137138The relay Durable Object's SQLite database stores compressed Git object chunks,139refs and receipts. The backend removes the former R2 per-push root and140transaction ceiling while preserving accepted-state authority, retry receipts141and purgatory behavior. It keeps bounded object, byte, ref and receipt limits,142and does not promise lower cost for every repository. SQLite storage is charged143through the existing events allowance. The Worker depends on the `ntig` package, whose source and package artifact144are recorded in [Vendored dependencies](../vendor/README.md). Its repository contract validates pack145structure, deltas, object dependencies, trees and commits before publication.146The owner can tune the Git capacity in the console's Rules → Git capacity panel,147or in an exported configuration under `policy.git`. Values are bytes unless148noted otherwise; omitted fields keep the current values. The editor validates149against the same maximums used by the runtime. For example, a larger personal150relay can set a 2 GiB retained Git allowance and 512 repositories:151152```json153{154 "format": "bind.ws/relay-config/2",155 "policy": {156 "git": {157 "maxRepositories": 512,158 "maxRelayBytes": 2147483648,159 "maxCompressedBytes": 1073741824,160 "maxPackBytes": 99614720,161 "maxObjectBytes": 67108864,162 "maxTransactionRawBytes": 67108864163 }164 }165}166```167168The defaults are:169170| Scope | Limit | What it controls |171|---|---:|---|172| Relay | 128 repositories, 2 GiB Git storage | accepted repositories and retained compressed Git objects plus metadata across the relay |173| Repository | 16 MiB per pack or object | one upload and one stored object |174| Repository | 4 GiB raw, 1 GiB compressed and 128 MiB metadata | retained object payloads and their SQL records |175| Repository | 4,096 refs, 100,000 objects and 1,000,000 graph edges | ref maps and verified object graphs |176| Repository | receipt metadata within the metadata limit | retry records without a separate transaction-count ceiling |177178The configurable maximums are 4,096 repositories, 6 GiB relay storage, 95 MiB179packs, 64 MiB objects, 64 GiB raw data, 6 GiB compressed data and 1 GiB180metadata per repository. Request limits are separately capped at 64 MiB and181100,000 objects per transaction, with 6 GiB fetch responses. These ceilings182protect the Worker and SQLite database; raising a relay's settings does not183remove storage, CPU, bandwidth or fuel costs. The per-relay physical SQLite184ceiling and backup/archive limits remain separate operational constraints.185186The SQL object store reads and writes chunks, refs and receipts in native SQL187transactions. Clone and fetch stream selected objects from the index. Pushes remain bounded188by the per-request pack and decoded-byte budgets; a large initial history may189need staged pushes. Fetch currently sends complete requested reachable history,190so incremental fetches may transfer objects the client already has. Compaction, orphan collection and191production capacity measurements remain follow-up work. Large repositories192should use ordinary client-side `git clone --mirror` or `git bundle` recovery;193the portable relay backup is intentionally bounded and may omit Git payloads.194Expired unknown195PR refs stay hidden when cleanup cannot commit, while immutable object history196keeps its bytes until the relay is torn down.197198Git HTTP walks indexed object metadata to check reachability and filters before199loading selected object bodies. Every write rechecks authority inside the SQL200transaction, which publishes objects, refs, receipts and pending-event201visibility together. The relay admits one repository operation at a time.202203The owner can request `gitstorage <repository-owner-hex> <identifier>` through204the management API to inspect the repository's object, ref and receipt totals.205The report includes compressed payload, raw object and metadata bytes plus the206relay's complete SQLite size. The diagnostic does not change stored objects,207refs or receipts.208209Reports read indexed SQL totals with a 60-second cooldown per live relay210instance. A restart resets that cooldown. Git requests,211event writes and configuration changes receive a retry response while a scan212owns the relay. See [HTTP reference](14-http-reference.md) for the method and213[Costs and margins](15-costs-and-margins.md) for its host costs and limits.214215Portable relay backups include compressed Git objects, refs and retry receipts216within the archive's 8 MiB and 12,000-entry limits. The owner can request a217configuration/events/media backup with Git omitted when repositories are too218large for this bounded format. Restore verifies repository identity, object219hashes, dependency graphs and the receipt chain before writing the fresh220target. See [Data and names](04-data-and-names.md).221222## Proactive synchronization and archive223224With `grasp02`, the relay performs repository-scoped historic and bounded live225event synchronization from accepted announcement relay lists. Missing Git data226is queued from signed state and accepted PR/update clone sources with an hourly227target subject to queued work, fuel and size limits. At most 16 Git sources are228considered; each connected round is bounded to 10 seconds. Failures retry after229five minutes before returning to the hourly schedule. Packs use `policy.git.maxPackBytes` (16 MiB by default), with 1 MiB of230additional wire response allowance. The same configured object and transaction231bounds apply to remote verification. Remote232advertisements never authorize refs; signed state and accepted events are233rechecked under the repository fence. PR objects already uploaded to this234relay through GRASP-06 are copied locally into the target repository, without235a self-fetch. Multiple clone URLs in a single tag are considered.236237History uses durable cursors with inclusive timestamp overlap and a 256-event238page cap. A source page cap is surfaced as partial history rather than silently239reported complete. Up to 512 durable sync jobs and 128 history windows are240retained. Restarts resume unfinished history before following the bounded 1.5241second live tail.242243With `grasp03`, accepted issue, patch and PR threads are expanded through the244authors' kind 10002 write relays. Replies may use `#e` or `#E` and do not need245a new repository coordinate; the accepted parent thread remains the scope246boundary. Only write relays from kind 10002 outbox lists are used. Metadata247refresh is limited to kinds 0, 10002 and 10317, with the same bounded source248and job limits.249250With `grasp05`, the relay may admit announcements that omit its own clone and251relay URLs. Archive admission still consumes the normal repository quota and252storage accounting, and depends on GRASP-02. Immutable packs remain retained253and charged; expiry does not provide garbage collection.254255The owner can inspect synchronization through `storagestats`. Its256`graspSync` result records whether synchronization is enabled, whether source257or scope caps made the result partial, event and Git job counts, durable error258counts, unfinished history windows, and the earliest due Unix timestamp (or259`null`). Error details remain in durable SQL rows. Backups preserve optional260`state.prRefs` rows, including `until: 0` tombstones for accepted or deleted261events; backups made before this field was added remain readable.262263## Information document and limits264265When GRASP is on and reads are open, NIP-11 includes:266267| Field | Meaning |268|---|---|269| `supported_grasps` | Lists only locally validated and enabled profiles; dependencies still apply. |270| `repo_acceptance_criteria` | who may announce repositories under the current write and kind rules, guest exceptions, required relay listing and storage limits |271| `curation` | omitted unless the owner applies curation beyond generic spam, bans or fuel rules |272273GitWorkshop classifies the acceptance description as public or approval274required. Open writes allow anyone to announce a repository; allowlist,275owner-only and web-of-trust writes describe their eligible authors. A guest276exception for kind 30617 opens announcements, while state and collaboration277events still follow their own rules. A blocked announcement kind preserves278the owner's existing kind-rule exemption. Changing a rule changes the279description without granting any new permission.280281NIP-11 never claims GRASP-08. A relay's regular read rule still governs the Nostr side, and turning GRASP off282removes its Git door and advertisement without changing the NIP-5A site283origins.284285## Limits under load286287One Git operation runs at a time for a relay. The operation fence also blocks288relay-side event and control mutations while a Git transaction reads or289writes its object graph. This bounds Worker memory and keeps the SQL object290snapshot and the relay's authority view from changing underneath one operation.291292Git requests consume the relay's normal address rate limits. A busy fence or293rate limit answers `429` with a retry reason. Fuel exhaustion, an unclaimed294relay, a restricted read rule or a repository limit answers a `restricted:`295response. A caller retries after the stated condition clears; it does not296assume that a rejected receive-pack changed refs.297298## What this does not promise299300The preview extensions do not promise complete history, uninterrupted live301connections, unconditional hourly Git work, private repositories, or an302upstream conformance result. Work is bounded by queued jobs, fuel, source,303window, byte and storage limits. The repository's Git object store is an304implementation detail; clients rely on the NIP-34 events and GRASP HTTP305contract, not on a particular backend.306307See [Your relay on the web](05-your-relay-on-the-web.md) for the user-facing308site and domain doors, [Architecture](10-architecture.md) for the Worker and309Durable Object boundaries, and [HTTP reference](14-http-reference.md) for the310exact request paths and statuses.