Files
ngit-grasp/docs/how-to/enable-grasp-06.md
DanConwayDev 3f8157693f docs: point GRASP references at Nostr Git
Move current GRASP specification links from GitHub to GitWorkshop.

Link audit output to its exact pinned specification commit.

Use NIP-05 cloning for ngit-grasp and ngit.dev for the ngit homepage.

Leave the archived migration link unchanged as a historical record.

Validated with rustfmt and the 54-test grasp-audit library suite.
2026-09-04 13:59:46 +00:00

4.8 KiB

How to enable GRASP-06 contributor PR submission

GRASP-06 adds an opt-in endpoint at /prs/<npub>/<identifier>.git that any contributor can push PR (kind 1618) and PR Update (kind 1619) refs/nostr/<event-id> refs to, even when this relay has no accepted announcement for the target repository. The endpoint is unauthenticated at the HTTP level — validity is established by the signed PR event.

This page covers the operator-facing steps to turn it on and verify it.

All paths below are relative to NGIT_BASE_PATH. For a relay mounted at /grasp, use /grasp, /grasp/prs/..., and matching path-prefixed clone tags in the examples.

1. Flip the feature flag

Set NGIT_GRASP06_ENABLE=true for the relay process. The flag is off by default; everything below is a no-op without it.

Environment file (.env):

NGIT_GRASP06_ENABLE=true

CLI flag:

ngit-grasp --grasp06-enable ...

NixOS module:

services.ngit-grasp.instances.<name>.grasp06Enable = true;

Restart the relay after the change.

2. Verify NIP-11 advertises GRASP-06

NIP-11 is served from / with the Accept: application/nostr+json header:

curl -s -H 'Accept: application/nostr+json' https://your-relay.example/ | jq '.supported_grasps'

The output must include "GRASP-06". If it does not, the flag did not take effect — re-check the env var name and that the relay actually restarted.

3. Verify /prs/ is reachable

Pick any well-formed npub and any identifier. A non-existent contributor + repo combination is fine — the endpoint synthesises an empty bare repo for fetches against paths that don't yet exist:

git clone https://your-relay.example/prs/npub1.../any-identifier.git /tmp/probe

You should get a successful clone of an empty repository (zero refs). No directory is created on the server for this probe.

To verify a contributor push round-trip, publish a kind 1618 PR event whose clone tag names this relay's /prs/<signer-npub>/<d>.git URL, then:

git push https://your-relay.example/prs/<signer-npub>/<d>.git \
    <commit-sha>:refs/nostr/<event-id>

The push succeeds, the relay creates <git_data_path>/prs/<signer-hex>/<d>.git on demand, and the ref is locked into the repo. Pushes to anything other than refs/nostr/<64-lowercase-hex> are rejected with an ERR pkt-line.

Storage cost

One bare repo per (submitter, identifier) combination under <git_data_path>/prs/<submitter-hex>/<identifier>.git. Repos are garbage-collected inline at the three runtime sites that can leave one empty, plus a startup scan that recovers anything left behind by a previous run — there is no separate periodic sweep over <git_data_path>/prs/:

  • After receive-pack, the repo is removed immediately if it has zero refs left (probe pushes that produced no valid state).
  • When a PR event arrives that fails validation against a scoped /prs/ placeholder, the corresponding refs/nostr/<event-id> ref is deleted; if that leaves the repo with zero refs the directory is removed in the same step.
  • When a scoped placeholder expires from purgatory without a matching PR event (default 30 minutes), the standard purgatory sweep deletes the dangling ref and, if it leaves the repo zero-ref, the bare repo itself.
  • On startup, before the HTTP server accepts requests, the /prs/ subtree is scanned once and any zero-ref bare repos (left by a crash mid-push, crash mid-cleanup, or shutdown with in-memory state lost) are removed.

All three sites coordinate through a per-(submitter, identifier) mutex and an in_flight counter; cleanup paths only rm -rf the bare repo while in_flight == 0, so an in-flight push cannot have its repo deleted out from under it. The mutex is held only briefly at each site — pack uploads and per-ref validation run lock-free, so concurrent pushes by multiple agents using the same identity do not serialise behind each other.

There is no quota in this release. Disk consumption is bounded only by the rate at which contributors push valid PR refs.

Abuse controls

The current release relies entirely on:

  • the signed PR / PR Update event (no NIP-98 or other HTTP auth on push),
  • the requirement that the event's clone tag names this relay's /prs/<signer>/<d>.git endpoint,
  • the standard 30-minute purgatory TTL, and
  • the inline zero-ref cleanups described under "Storage cost".

The following knobs are future additions and are not yet wired:

  • per-submitter allowlist,
  • per-submitter or per-event disk quotas,
  • per-ref pack size cap,
  • NIP-98 authenticated push,
  • PoW gating.

If you need any of these today, leave NGIT_GRASP06_ENABLE=false.

Spec

GRASP-06 spec (draft). Design notes: docs/explanation/grasp-06-contributor-pr-submission.md.