mirror of
https://relay.ngit.dev/npub15qydau2hjma6ngxkl2cyar74wzyjshvl65za5k5rl69264ar2exs5cyejr/ngit-grasp.git
synced 2026-10-05 23:18:24 +00:00
feat(sync): make Sync+ operator-controllable
Add a default-on Sync+ switch across CLI/environment, NixOS, examples, and reference documentation. The switch gates only the GRASP-03 NIP-65 mailbox overlay, preserving ordinary GRASP-02 proactive sync when operators opt out. Advertise GRASP-03 through the established NIP-11 supported_grasps extension only when the overlay is enabled. Focused configuration and relay-information tests cover the default and opt-out behavior. This deliberately does not change descendant sync algorithms, add missing-list fallbacks, or introduce a separate proactive-sync lifecycle.
This commit is contained in:
@@ -120,6 +120,11 @@
|
||||
# Default: (none - relay discovery from stored announcements only)
|
||||
# NGIT_SYNC_BOOTSTRAP_RELAY_URL=wss://relay.example.com
|
||||
|
||||
# Enable GRASP-03 Sync+ mailbox discovery on top of proactive GRASP-02 sync
|
||||
# CLI: --sync-plus-enabled <true|false>
|
||||
# Default: true
|
||||
# NGIT_SYNC_PLUS_ENABLED=true
|
||||
|
||||
# Relays used to discover eligible accepted repository participants' NIP-65 relay lists
|
||||
# CLI: --user-index-relays <comma-separated-websocket-urls>
|
||||
# Default: wss://purplepag.es,wss://index.hzrd149.com,wss://indexer.coracle.social
|
||||
|
||||
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Add a default-on `NGIT_SYNC_PLUS_ENABLED` opt-out and advertise GRASP-03 in
|
||||
NIP-11 only when the Sync+ overlay is effective.
|
||||
- Recover repository-event descendants which reference a direct thread member
|
||||
but omit the repository and root-event tags. When the per-connection ledger
|
||||
can retain complete descendant coverage while preserving control and
|
||||
|
||||
@@ -580,6 +580,11 @@ The ngit-grasp relay implements **Proactive Sync of Nostr Events**, which synchr
|
||||
|
||||
For full design details, see [grasp-02-proactive-sync.md](grasp-02-proactive-sync.md).
|
||||
|
||||
GRASP-03 Sync+ is a default-on mailbox-discovery overlay on this manager. Set
|
||||
`NGIT_SYNC_PLUS_ENABLED=false` to retain GRASP-02 sync without discovering
|
||||
root-author NIP-65 inboxes; NIP-11 advertises `GRASP-03` only while the overlay
|
||||
is enabled.
|
||||
|
||||
### Rejected Events Index
|
||||
|
||||
The rejected events index solves two critical problems during sync:
|
||||
|
||||
@@ -4,6 +4,11 @@ GRASP-03 extends repository-declared GRASP-02 coverage to the Nostr outbox
|
||||
model. An accepted issue, patch or pull request may have replies in the root
|
||||
author's inbox even when those events are absent from repository relays.
|
||||
|
||||
The overlay is enabled by default and can be disabled with
|
||||
`NGIT_SYNC_PLUS_ENABLED=false`. Because it extends the proactive GRASP-02
|
||||
manager, it is effective only while that manager is running. NIP-11 advertises
|
||||
`GRASP-03` only when the overlay is enabled.
|
||||
|
||||
## Minimal approach
|
||||
|
||||
The implementation adds a narrow discovery control-plane, not a second sync
|
||||
|
||||
@@ -290,6 +290,25 @@ NGIT_DATABASE_BACKEND=memory
|
||||
|
||||
These options configure the proactive sync feature that synchronizes events from other relays.
|
||||
|
||||
#### `NGIT_SYNC_PLUS_ENABLED`
|
||||
|
||||
**Description:** Enable GRASP-03 Sync+ mailbox discovery on top of proactive GRASP-02 sync
|
||||
**Type:** Boolean
|
||||
**Default:** `true`
|
||||
**Required:** No
|
||||
|
||||
```bash
|
||||
# Opt out of mailbox discovery while retaining ordinary proactive sync
|
||||
NGIT_SYNC_PLUS_ENABLED=false
|
||||
```
|
||||
|
||||
The corresponding NixOS option is `syncPlusEnabled`. The setting is effective
|
||||
only while the relay service's proactive sync manager is running. When false,
|
||||
the relay retains GRASP-02 sync but does not discover NIP-65 inboxes and omits
|
||||
`GRASP-03` from its NIP-11 `supported_grasps` list.
|
||||
|
||||
---
|
||||
|
||||
#### `NGIT_SYNC_BOOTSTRAP_RELAY_URL`
|
||||
|
||||
**Description:** URL of the bootstrap relay to initially sync events from
|
||||
|
||||
@@ -103,6 +103,16 @@ let
|
||||
description = "Bootstrap relay URL to sync from on startup (optional)";
|
||||
};
|
||||
|
||||
syncPlusEnabled = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Enable GRASP-03 Sync+ mailbox discovery on top of proactive
|
||||
GRASP-02 sync. This has no effect unless the relay service, and
|
||||
therefore its proactive sync manager, is enabled.
|
||||
'';
|
||||
};
|
||||
|
||||
userIndexRelays = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [
|
||||
@@ -486,6 +496,7 @@ let
|
||||
toString cfg.metricsConnectionPerIpAbuseThreshold;
|
||||
NGIT_METRICS_TOP_N_REPOS = toString cfg.metricsTopNRepos;
|
||||
NGIT_SYNC_MAX_BACKOFF_SECS = toString cfg.syncMaxBackoffSecs;
|
||||
NGIT_SYNC_PLUS_ENABLED = if cfg.syncPlusEnabled then "true" else "false";
|
||||
NGIT_SYNC_DISCONNECT_CHECK_INTERVAL_SECS =
|
||||
toString cfg.syncDisconnectCheckIntervalSecs;
|
||||
NGIT_SYNC_BASE_BACKOFF_SECS = toString cfg.syncBaseBackoffSecs;
|
||||
|
||||
@@ -389,6 +389,15 @@ pub struct Config {
|
||||
#[arg(long, env = "NGIT_SYNC_BOOTSTRAP_RELAY_URL")]
|
||||
pub sync_bootstrap_relay_url: Option<String>,
|
||||
|
||||
/// Enable GRASP-03 Sync+ mailbox discovery on top of proactive GRASP-02 sync.
|
||||
#[arg(
|
||||
long,
|
||||
env = "NGIT_SYNC_PLUS_ENABLED",
|
||||
default_value_t = true,
|
||||
action = clap::ArgAction::Set
|
||||
)]
|
||||
pub sync_plus_enabled: bool,
|
||||
|
||||
/// Comma-separated relays used to discover eligible accepted repository participants'
|
||||
/// NIP-65 relay lists.
|
||||
#[arg(
|
||||
@@ -1101,6 +1110,7 @@ impl Config {
|
||||
metrics_connection_per_ip_abuse_threshold: 10,
|
||||
metrics_top_n_repos: 10,
|
||||
sync_bootstrap_relay_url: None,
|
||||
sync_plus_enabled: true,
|
||||
user_index_relays: DEFAULT_USER_INDEX_RELAYS.to_string(),
|
||||
sync_max_backoff_secs: 3600,
|
||||
sync_disconnect_check_interval_secs: 60,
|
||||
@@ -1220,6 +1230,22 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sync_plus_is_enabled_by_default_and_can_be_disabled() {
|
||||
let default = Config::try_parse_from(["ngit-grasp", "--domain", "example.com"])
|
||||
.expect("Sync+ default should parse");
|
||||
assert!(default.sync_plus_enabled);
|
||||
|
||||
let disabled = Config::try_parse_from([
|
||||
"ngit-grasp",
|
||||
"--domain",
|
||||
"example.com",
|
||||
"--sync-plus-enabled=false",
|
||||
])
|
||||
.expect("Sync+ opt-out should parse");
|
||||
assert!(!disabled.sync_plus_enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_index_relays_parse_cli_list_and_ignore_empty_entries() {
|
||||
let config = Config::try_parse_from([
|
||||
|
||||
+26
-7
@@ -80,6 +80,9 @@ impl RelayInformationDocument {
|
||||
supported_grasps.push("GRASP-05".to_string());
|
||||
}
|
||||
supported_grasps.push("GRASP-02".to_string());
|
||||
if config.sync_plus_enabled {
|
||||
supported_grasps.push("GRASP-03".to_string());
|
||||
}
|
||||
if config.grasp06_enable {
|
||||
supported_grasps.push("GRASP-06".to_string());
|
||||
}
|
||||
@@ -197,8 +200,10 @@ mod tests {
|
||||
// (disrespector off).
|
||||
assert!(doc.supported_nips.contains(&9));
|
||||
assert!(doc.supported_nips.contains(&62));
|
||||
// Without archive mode, only GRASP-01 and GRASP-02
|
||||
assert_eq!(doc.supported_grasps, vec!["GRASP-01", "GRASP-02"]);
|
||||
assert_eq!(
|
||||
doc.supported_grasps,
|
||||
vec!["GRASP-01", "GRASP-02", "GRASP-03"]
|
||||
);
|
||||
assert!(doc.repo_acceptance_criteria.contains("None"));
|
||||
assert!(doc.curation.is_none());
|
||||
assert_eq!(
|
||||
@@ -236,6 +241,7 @@ mod tests {
|
||||
assert_eq!(parsed["name"], "Test Relay");
|
||||
assert_eq!(parsed["supported_grasps"][0], "GRASP-01");
|
||||
assert_eq!(parsed["supported_grasps"][1], "GRASP-02");
|
||||
assert_eq!(parsed["supported_grasps"][2], "GRASP-03");
|
||||
assert_eq!(parsed["icon"], "https://relay.example.com/icon.png");
|
||||
assert_eq!(parsed["limitation"]["max_subscriptions"], 500);
|
||||
assert_eq!(parsed["limitation"]["max_limit"], 500);
|
||||
@@ -268,7 +274,7 @@ mod tests {
|
||||
// Archive mode enabled: should include GRASP-05
|
||||
assert_eq!(
|
||||
doc.supported_grasps,
|
||||
vec!["GRASP-01", "GRASP-05", "GRASP-02"]
|
||||
vec!["GRASP-01", "GRASP-05", "GRASP-02", "GRASP-03"]
|
||||
);
|
||||
// Archive read-only: should have curation field
|
||||
assert!(doc.curation.is_some());
|
||||
@@ -291,7 +297,7 @@ mod tests {
|
||||
// Archive whitelist enabled: should include GRASP-05
|
||||
assert_eq!(
|
||||
doc.supported_grasps,
|
||||
vec!["GRASP-01", "GRASP-05", "GRASP-02"]
|
||||
vec!["GRASP-01", "GRASP-05", "GRASP-02", "GRASP-03"]
|
||||
);
|
||||
// Archive read-only defaults to true: should have curation field
|
||||
assert!(doc.curation.is_some());
|
||||
@@ -312,7 +318,10 @@ mod tests {
|
||||
let doc = RelayInformationDocument::from_config(&config);
|
||||
|
||||
// Repository whitelist doesn't enable GRASP-05
|
||||
assert_eq!(doc.supported_grasps, vec!["GRASP-01", "GRASP-02"]);
|
||||
assert_eq!(
|
||||
doc.supported_grasps,
|
||||
vec!["GRASP-01", "GRASP-02", "GRASP-03"]
|
||||
);
|
||||
// Should have curation field for repository whitelist
|
||||
assert!(doc.curation.is_some());
|
||||
assert!(doc
|
||||
@@ -336,7 +345,7 @@ mod tests {
|
||||
// Should have GRASP-05 enabled due to archive whitelist
|
||||
assert_eq!(
|
||||
doc.supported_grasps,
|
||||
vec!["GRASP-01", "GRASP-05", "GRASP-02"]
|
||||
vec!["GRASP-01", "GRASP-05", "GRASP-02", "GRASP-03"]
|
||||
);
|
||||
// Should have curation field reflecting BOTH archive and repository whitelist
|
||||
assert!(doc.curation.is_some());
|
||||
@@ -366,10 +375,20 @@ mod tests {
|
||||
// to match the insertion order in from_config().
|
||||
assert_eq!(
|
||||
doc.supported_grasps,
|
||||
vec!["GRASP-01", "GRASP-02", "GRASP-06"]
|
||||
vec!["GRASP-01", "GRASP-02", "GRASP-03", "GRASP-06"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nip11_omits_grasp_03_when_sync_plus_is_disabled() {
|
||||
let mut config = Config::for_testing();
|
||||
config.sync_plus_enabled = false;
|
||||
|
||||
let doc = RelayInformationDocument::from_config(&config);
|
||||
|
||||
assert_eq!(doc.supported_grasps, vec!["GRASP-01", "GRASP-02"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nip11_advertises_deletion_by_default() {
|
||||
let config = Config::for_testing();
|
||||
|
||||
@@ -4816,6 +4816,13 @@ impl SyncManager {
|
||||
}
|
||||
|
||||
async fn schedule_nip65_discovery(&mut self) {
|
||||
// GRASP-03 is an optional overlay on the always-running GRASP-02
|
||||
// manager. When disabled, do not inventory authors, retain identity
|
||||
// authority, open discovery connections, or add inbox roots.
|
||||
if !self.config.sync_plus_enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
if self
|
||||
.nip65_discovery
|
||||
|
||||
Reference in New Issue
Block a user