mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 23:18:24 +00:00
feat(grasp06): add NGIT_GRASP06_ENABLE feature flag and NIP-11 advertisement
Introduces the `grasp06_enable` config flag, default false, wired through all four config-sync locations (src/config.rs, .env.example, docs/reference/configuration.md, nix/module.nix), and advertises the capability in NIP-11 when the flag is on. Behaviour: - When NGIT_GRASP06_ENABLE=true: NIP-11 supported_grasps includes "GRASP-06" (after GRASP-01 and GRASP-02). No /prs/ routing or event-acceptance changes yet — those land later. - When disabled (default): unchanged. Tests turning green: - test_nip11_advertises_grasp_06_when_enabled (integration, with_grasp_06) - test_nip11_advertises_grasp_06_when_enabled (unit, src/http/nip11.rs) - test_nip11_grasp_06_disabled_by_default (unit) The discovery-gate test `test_prs_namespace_404_when_grasp06_not_advertised_no_grasp_06` remains green and now exercises the real flag and real NIP-11 wiring. `with_grasp_06` tests targeting /prs/ routing and event-acceptance remain TDD-red against later work. See: plans/grasp-06-implementation-plan.md (NIP-11 advertisement folded in)
This commit is contained in:
@@ -219,6 +219,25 @@
|
||||
# Note: Cannot be used with NGIT_REPOSITORY_WHITELIST (mutually exclusive)
|
||||
# NGIT_ARCHIVE_READ_ONLY=
|
||||
|
||||
# ============================================================================
|
||||
# GRASP-06 CONTRIBUTOR PR SUBMISSION
|
||||
# ============================================================================
|
||||
|
||||
# Enable GRASP-06 contributor PR submission endpoint at /prs/<npub>/<identifier>.git
|
||||
#
|
||||
# When enabled, the relay exposes an unauthenticated PR submission endpoint that
|
||||
# accepts pushes of refs/nostr/<event-id> from any contributor. Security relies
|
||||
# on the signed PR/PR-Update events the refs reference, not on HTTP-level auth.
|
||||
#
|
||||
# When disabled (default), /prs/* returns 404 and event-acceptance is unchanged.
|
||||
#
|
||||
# See: https://github.com/DanConwayDev/grasp/blob/main/06.md
|
||||
# See: docs/explanation/grasp-06-contributor-pr-submission.md
|
||||
#
|
||||
# CLI: --grasp06-enable
|
||||
# Default: false
|
||||
# NGIT_GRASP06_ENABLE=false
|
||||
|
||||
# ============================================================================
|
||||
# REPOSITORY WHITELIST
|
||||
# ============================================================================
|
||||
|
||||
@@ -720,6 +720,51 @@ NGIT_ARCHIVE_READ_ONLY=true # Default
|
||||
|
||||
---
|
||||
|
||||
### GRASP-06 Contributor PR Submission
|
||||
|
||||
These options control the optional `/prs/<npub>/<identifier>.git` contributor pull-request submission endpoint per the [GRASP-06 specification](https://github.com/DanConwayDev/grasp/blob/main/06.md).
|
||||
|
||||
#### `NGIT_GRASP06_ENABLE`
|
||||
|
||||
**Description:** Enable the GRASP-06 contributor PR submission endpoint at `/prs/<npub>/<identifier>.git`
|
||||
**Type:** Boolean
|
||||
**Default:** `false`
|
||||
**Required:** No
|
||||
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Enable GRASP-06 (opt-in)
|
||||
NGIT_GRASP06_ENABLE=true
|
||||
|
||||
# Disable (default)
|
||||
NGIT_GRASP06_ENABLE=false
|
||||
```
|
||||
|
||||
**Behavior:**
|
||||
|
||||
- When `true`:
|
||||
- `/prs/<npub>/<identifier>.git` accepts unauthenticated pushes of `refs/nostr/<event-id>`
|
||||
- NIP-11 `supported_grasps` includes `"GRASP-06"`
|
||||
- PR / PR-Update events naming this relay's `/prs/` endpoint in their `clone` tag are accepted to purgatory even without a matching accepted repository announcement
|
||||
- Standard `<npub>/<identifier>.git` endpoint behaviour is unchanged
|
||||
- When `false` (default):
|
||||
- `/prs/*` returns HTTP 404
|
||||
- Event-acceptance is unchanged (standard GRASP-01 rules apply)
|
||||
|
||||
**Security Model:**
|
||||
|
||||
The `/prs/` endpoint is intentionally unauthenticated — there is no NIP-98 auth, no allowlist, no quota, and no proof-of-work in v1. Validity is enforced by:
|
||||
|
||||
- The signed PR / PR-Update Nostr event the pushed `refs/nostr/<event-id>` references
|
||||
- Per-submitter / per-identifier scoping enforced when the event arrives
|
||||
- Inline ref-name validation (only `refs/nostr/<64-hex-event-id>` accepted)
|
||||
- Periodic cleanup of orphan repositories with zero refs
|
||||
|
||||
Operators should review the design tradeoffs in [`docs/explanation/grasp-06-contributor-pr-submission.md`](../explanation/grasp-06-contributor-pr-submission.md) before enabling.
|
||||
|
||||
---
|
||||
|
||||
### Repository Whitelist
|
||||
|
||||
#### `NGIT_REPOSITORY_WHITELIST`
|
||||
|
||||
@@ -228,6 +228,25 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
grasp06Enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable the GRASP-06 contributor PR submission endpoint at
|
||||
/prs/<npub>/<identifier>.git.
|
||||
|
||||
When enabled, the relay exposes an unauthenticated PR submission
|
||||
endpoint that accepts pushes of refs/nostr/<event-id> from any
|
||||
contributor. Security relies on the signed PR/PR-Update events the
|
||||
refs reference, not on HTTP-level auth.
|
||||
|
||||
When disabled (default), /prs/* returns 404 and event acceptance is
|
||||
unchanged.
|
||||
|
||||
See: https://github.com/DanConwayDev/grasp/blob/main/06.md
|
||||
'';
|
||||
};
|
||||
|
||||
repositoryWhitelist = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
@@ -339,6 +358,7 @@ let
|
||||
NGIT_REPOSITORY_BLACKLIST = concatStringsSep "," cfg.repositoryBlacklist;
|
||||
NGIT_EVENT_BLACKLIST = concatStringsSep "," cfg.eventBlacklist;
|
||||
NGIT_LOG_LEVEL = cfg.logLevel;
|
||||
NGIT_GRASP06_ENABLE = if cfg.grasp06Enable then "true" else "false";
|
||||
} // optionalAttrs (cfg.maxConnections != null) {
|
||||
NGIT_MAX_CONNECTIONS = toString cfg.maxConnections;
|
||||
} // optionalAttrs (cfg.relayName != null) {
|
||||
|
||||
@@ -449,6 +449,20 @@ pub struct Config {
|
||||
#[arg(long, env = "NGIT_ARCHIVE_READ_ONLY")]
|
||||
pub archive_read_only: Option<bool>,
|
||||
|
||||
/// Enable GRASP-06 contributor PR submission endpoint at /prs/<npub>/<identifier>.git
|
||||
///
|
||||
/// When enabled, the relay exposes an unauthenticated PR submission endpoint
|
||||
/// at `/prs/<npub>/<identifier>.git` that accepts pushes of `refs/nostr/<event-id>`
|
||||
/// from any contributor. Security relies on the signed PR/PR-Update events
|
||||
/// the refs reference, not on HTTP-level auth.
|
||||
///
|
||||
/// Default: false. This is an opt-in feature that adds an unauthenticated
|
||||
/// write surface; operators must understand the tradeoffs (see GRASP-06 spec
|
||||
/// and `docs/explanation/grasp-06-contributor-pr-submission.md`) before
|
||||
/// enabling it.
|
||||
#[arg(long, env = "NGIT_GRASP06_ENABLE", default_value_t = false)]
|
||||
pub grasp06_enable: bool,
|
||||
|
||||
/// Repository whitelist: comma-separated list of npub/identifier/npub/identifier entries
|
||||
/// Formats: "npub1...", "npub1.../identifier", "identifier"
|
||||
/// When set, only announcements matching the whitelist AND listing the service are accepted
|
||||
@@ -751,6 +765,7 @@ impl Config {
|
||||
archive_whitelist: String::new(),
|
||||
archive_grasp_services: String::new(),
|
||||
archive_read_only: None,
|
||||
grasp06_enable: false,
|
||||
repository_whitelist: String::new(),
|
||||
repository_blacklist: String::new(),
|
||||
event_blacklist: String::new(),
|
||||
|
||||
@@ -67,6 +67,9 @@ impl RelayInformationDocument {
|
||||
supported_grasps.push("GRASP-05".to_string());
|
||||
}
|
||||
supported_grasps.push("GRASP-02".to_string());
|
||||
if config.grasp06_enable {
|
||||
supported_grasps.push("GRASP-06".to_string());
|
||||
}
|
||||
|
||||
// Build curation field for archive read-only mode or repository whitelist
|
||||
let repository_config = config.repository_config();
|
||||
@@ -281,4 +284,29 @@ mod tests {
|
||||
assert!(curation.contains("whitelisted repositories"));
|
||||
assert!(curation.contains("with or without service listing"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nip11_grasp_06_disabled_by_default() {
|
||||
let config = Config::for_testing();
|
||||
let doc = RelayInformationDocument::from_config(&config);
|
||||
|
||||
// GRASP-06 must not be advertised when the flag is off.
|
||||
assert!(!doc.supported_grasps.iter().any(|g| g == "GRASP-06"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nip11_advertises_grasp_06_when_enabled() {
|
||||
let mut config = Config::for_testing();
|
||||
config.grasp06_enable = true;
|
||||
|
||||
let doc = RelayInformationDocument::from_config(&config);
|
||||
|
||||
// GRASP-06 must be advertised when the flag is on, alongside the
|
||||
// standard GRASP-01 / GRASP-02 entries. Order is "GRASP-06 last"
|
||||
// to match the insertion order in from_config().
|
||||
assert_eq!(
|
||||
doc.supported_grasps,
|
||||
vec!["GRASP-01", "GRASP-02", "GRASP-06"]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user