nostr-bray 1.27.3 → 1.27.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,25 @@ export interface PoolLike {
6
6
  querySync(relays: string[], filter: Filter): Promise<NostrEvent[]>;
7
7
  destroy(): void;
8
8
  }
9
+ /**
10
+ * Summarise a publish attempt into the two boolean flags on {@link PublishResult}.
11
+ *
12
+ * `success` is true when the event reached the network as a majority: at least
13
+ * one relay accepted AND at least 50% of attempted relays accepted. This is the
14
+ * default "did my publish work" signal. Paywalled, whitelisted, or transient
15
+ * failures routinely leave some relays in the rejected list, so requiring every
16
+ * relay to accept produces far too many false negatives.
17
+ *
18
+ * `allAccepted` preserves the strict "every attempted relay accepted" semantic
19
+ * for callers that genuinely need it (e.g. high-assurance publishing to a
20
+ * small curated list of private relays).
21
+ *
22
+ * Zero attempted relays is treated as failure on both flags.
23
+ */
24
+ export declare function summarisePublish(acceptedCount: number, attempted: number): {
25
+ success: boolean;
26
+ allAccepted: boolean;
27
+ };
9
28
  export interface RelayPoolConfig {
10
29
  torProxy?: string;
11
30
  allowClearnet: boolean;
@@ -1,3 +1,26 @@
1
+ /**
2
+ * Summarise a publish attempt into the two boolean flags on {@link PublishResult}.
3
+ *
4
+ * `success` is true when the event reached the network as a majority: at least
5
+ * one relay accepted AND at least 50% of attempted relays accepted. This is the
6
+ * default "did my publish work" signal. Paywalled, whitelisted, or transient
7
+ * failures routinely leave some relays in the rejected list, so requiring every
8
+ * relay to accept produces far too many false negatives.
9
+ *
10
+ * `allAccepted` preserves the strict "every attempted relay accepted" semantic
11
+ * for callers that genuinely need it (e.g. high-assurance publishing to a
12
+ * small curated list of private relays).
13
+ *
14
+ * Zero attempted relays is treated as failure on both flags.
15
+ */
16
+ export function summarisePublish(acceptedCount, attempted) {
17
+ if (attempted <= 0)
18
+ return { success: false, allAccepted: false };
19
+ return {
20
+ success: acceptedCount >= 1 && acceptedCount * 2 >= attempted,
21
+ allAccepted: acceptedCount === attempted,
22
+ };
23
+ }
1
24
  /** Initialise WebSocket and create a real SimplePool — lazy-loaded to avoid side effects at import */
2
25
  async function createRealPool(torProxy) {
3
26
  const { useWebSocketImplementation, SimplePool } = await import('nostr-tools/pool');
@@ -72,7 +95,7 @@ export class RelayPool {
72
95
  const relays = this.getRelays(npub);
73
96
  const writeRelays = relays.write;
74
97
  if (writeRelays.length === 0) {
75
- return { success: false, accepted: [], rejected: [], errors: ['no write relays configured'] };
98
+ return { success: false, allAccepted: false, accepted: [], rejected: [], errors: ['no write relays configured'] };
76
99
  }
77
100
  const promises = pool.publish(writeRelays, event);
78
101
  const accepted = [];
@@ -91,7 +114,7 @@ export class RelayPool {
91
114
  }
92
115
  }
93
116
  return {
94
- success: accepted.length === writeRelays.length,
117
+ ...summarisePublish(accepted.length, writeRelays.length),
95
118
  accepted,
96
119
  rejected,
97
120
  errors,
@@ -101,7 +124,7 @@ export class RelayPool {
101
124
  async publishDirect(relays, event) {
102
125
  const pool = await this.poolReady;
103
126
  if (relays.length === 0) {
104
- return { success: false, accepted: [], rejected: [], errors: ['no relays specified'] };
127
+ return { success: false, allAccepted: false, accepted: [], rejected: [], errors: ['no relays specified'] };
105
128
  }
106
129
  const promises = pool.publish(relays, event);
107
130
  const accepted = [];
@@ -120,7 +143,7 @@ export class RelayPool {
120
143
  }
121
144
  }
122
145
  return {
123
- success: accepted.length === relays.length,
146
+ ...summarisePublish(accepted.length, relays.length),
124
147
  accepted,
125
148
  rejected,
126
149
  errors,
@@ -123,7 +123,12 @@ export async function handlePublishScheduled(pool, npub, queueDir) {
123
123
  published++;
124
124
  }
125
125
  else {
126
- console.error(`Failed to publish ${data.event.id}: rejected by all relays`);
126
+ // success is the majority-quorum flag: treat as failure only when
127
+ // we did not reach at least 50% of attempted relays.
128
+ const reason = result.accepted.length === 0
129
+ ? 'rejected by all relays'
130
+ : `only ${result.accepted.length}/${result.accepted.length + result.rejected.length} relays accepted`;
131
+ console.error(`Failed to publish ${data.event.id}: ${reason}`);
127
132
  failed++;
128
133
  }
129
134
  }
@@ -25,7 +25,7 @@ export function registerSocialTools(server, deps) {
25
25
  maxEntries: deps.veilCacheMax ?? 500,
26
26
  });
27
27
  server.registerTool('social-post', {
28
- description: 'Post a text note (kind 1) signed by the active identity and publish to relays. Returns { id, pubkey, publish: { success, accepted, rejected } }. The most common social action.',
28
+ description: 'Post a text note (kind 1) signed by the active identity and publish to relays. Returns { id, pubkey, publish: { success, allAccepted, accepted, rejected } } where success is the majority-quorum signal (true when at least one relay accepted and at least half of attempted relays accepted) and allAccepted is true only when every attempted relay accepted. The most common social action.',
29
29
  inputSchema: {
30
30
  content: z.string().describe('Text content of the note'),
31
31
  },
package/dist/types.d.ts CHANGED
@@ -21,7 +21,18 @@ export interface RelaySet {
21
21
  }
22
22
  /** Result of a publish operation */
23
23
  export interface PublishResult {
24
+ /**
25
+ * True when the event is reliably published: at least one relay accepted
26
+ * AND at least 50% of attempted relays accepted. Captures both "reached
27
+ * the network" and "reached a majority", which matches real-world
28
+ * expectations where paywalled or whitelisted relays routinely reject.
29
+ */
24
30
  success: boolean;
31
+ /**
32
+ * True when every attempted relay accepted. Use this for high-assurance
33
+ * publishing to a small curated list of private relays.
34
+ */
35
+ allAccepted: boolean;
25
36
  accepted: string[];
26
37
  rejected: string[];
27
38
  errors: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nostr-bray",
3
- "version": "1.27.3",
3
+ "version": "1.27.4",
4
4
  "description": "Trust-aware Nostr for AI and humans. Three dimensions of trust -- verification, proximity, and access -- woven into every interaction.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",