Skip to content

Staging - #109

Merged
AustinKelsay merged 32 commits into
mainfrom
staging
Jul 18, 2026
Merged

Staging#109
AustinKelsay merged 32 commits into
mainfrom
staging

Conversation

@AustinKelsay

@AustinKelsay AustinKelsay commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added a shared, structured diagnostic logger contract and exposed compatible logger exports across Node and web.
    • Added quiet-by-default diagnostics for zap clients and ephemeral relays (with optional injected logging).
    • Added an automated “command reference” inventory verifier for scripts and README command listings.
  • Bug Fixes

    • Improved relay connection/disconnect and shutdown/restart coordination.
    • Ensured NIP-86 request timeouts are cleared on all outcomes.
    • Hardened NIP-57 zap receipt subscription cleanup and error handling.
  • Documentation

    • Reorganized README documentation into a Command Reference; updated NIP-02/NIP-47/NIP-57 guidance; removed obsolete example/script references.

AustinKelsay and others added 28 commits July 14, 2026 09:09
Server-side session shutdown did not prove that a same-process public Relay had observed its disconnect. Coordinate exact-URL observers at the WebSocket boundary, gate notification on owned transport shutdown, and make close finalization idempotent across natural, coordinated, manual, and late callback paths.
Eliminate remaining runtime and maintenance debt
@cursor

cursor Bot commented Jul 18, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4929c917-3c2d-4ccf-8665-f14769b34152

📥 Commits

Reviewing files that changed from the base of the PR and between 1d07a4b and df13432.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json

📝 Walkthrough

Walkthrough

This PR consolidates command documentation and verification, introduces shared diagnostic logger contracts with compatibility aliases, hardens relay and NIP client lifecycle behavior, adds public security and compatibility tests, and removes stale example and shim surfaces.

Changes

Runtime cleanup and contracts

Layer / File(s) Summary
Command inventory
package.json, scripts/verify-commands.js, README.md, tests/scripts/*, examples/*
Adds command-reference verification, removes obsolete scripts, updates aggregate example commands, and validates script graphs and README inventory consistency.
Diagnostic contracts
src/utils/logger.ts, src/index.ts, src/entries/index.web.ts, src/nip02/*, src/nip47/*
Adds shared diagnostic types, exposes them from both entries, and preserves NIP-02/NIP-47 logger aliases for compatibility.
Relay lifecycle
src/nip01/relay.ts, src/utils/ephemeral-relay.ts, src/utils/websocket.ts, tests/utils/*
Adds attempt-scoped disconnect finalization, observer isolation, structured relay logging, idempotent shutdown, transport reset, and lifecycle regression coverage.
NIP client cleanup
src/nip57/*, src/nip86/index.ts, tests/nip57/*, tests/nip86/*
Centralizes NIP-57 receipt collection and diagnostics, and ensures NIP-86 request timers are cleared across fetch, parsing, and error paths.
Validation coverage
tests/security/*, tests/types/*, tests/nip02/*, tests/entries/*
Adds public security-boundary tests, logger compatibility fixtures, warn-only logger coverage, and root/web export identity checks.
Documentation and release records
docs/adr/*, docs/agents/runs/*, CHANGELOG.md
Records cleanup decisions, verification sessions, and the 0.5.0 release metadata.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested labels: codex

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is just the branch name and does not describe the change set. Use a concise title that names the main change, such as diagnostic logging unification and command/reference cleanup.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch staging

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/utils/websocket.ts (1)

14-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the canonical relay URL normalizer.

This local implementation creates a second normalization contract, risking observer-key mismatches with other relay operations. Replace it with the existing utility.

As per coding guidelines, “Use the existing relay URL normalization utilities for relay operations.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/websocket.ts` around lines 14 - 20, Replace the local
normalizeWebSocketUrl implementation with the existing canonical relay URL
normalization utility, and update its callers to use that shared symbol. Remove
the duplicate try/catch normalization logic while preserving the current
WebSocket URL normalization behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/agents/runs/feature-cleanup-runtime-debt-ledger.md`:
- Line 12: Update the PR metadata references in the document’s current-status
entries to identify PR `#109` as the active staging-to-main review. Explicitly
label PR `#108` as historical wherever it remains mentioned, including the
additional referenced line.

In `@src/nip57/client.ts`:
- Around line 147-148: Replace the receipt collection array in the subscription
flow with a Map<string, NostrEvent> keyed by event.id, updating entries as
receipts arrive and resolving the map values before calculating totals or event
statistics. Preserve activeSubscriptionIds behavior, and add a multi-relay test
confirming identical receipts are counted only once.
- Around line 144-151: Update collectZapReceipts to use subscribeDetailed() and
track EOSE completion for every returned relay subscription, settling only once
all subscribed relays have completed or the legacy timeout fires; preserve
events from relays whose EOSE arrives later, including synchronous EOSE during
subscription setup. Add a staggered two-relay regression test using the
ephemeral relay setup to verify both relays’ events are collected before
resolution.

In `@src/utils/ephemeral-relay.ts`:
- Around line 177-184: Update the start() method to cache its in-flight startup
operation in a _startPromise, making concurrent calls await and reuse that
promise before returning the relay. Clear _startPromise after the startup
promise settles, while preserving the existing _closePromise handling and
already-started checks.
- Around line 134-146: The constructor’s purge interval handling must disable
scheduling for zero, negative, NaN, and Infinity values. Update the options
normalization and _purge assignment in the constructor to validate purgeInterval
consistently for both numeric and NostrRelayOptions inputs, preserving only
finite positive intervals and treating invalid or zero values as disabled.

---

Nitpick comments:
In `@src/utils/websocket.ts`:
- Around line 14-20: Replace the local normalizeWebSocketUrl implementation with
the existing canonical relay URL normalization utility, and update its callers
to use that shared symbol. Remove the duplicate try/catch normalization logic
while preserving the current WebSocket URL normalization behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c53f9d10-95b9-402e-8970-730729076f7d

📥 Commits

Reviewing files that changed from the base of the PR and between 7d601b6 and 1d07a4b.

📒 Files selected for processing (54)
  • CLAUDE.md
  • README.md
  • docs/adr/0002-unify-diagnostics-compatibly.md
  • docs/agents/runs/feature-cleanup-runtime-debt-ledger.md
  • docs/agents/runs/final-cleanup-review-session.md
  • docs/agents/runs/integrated-bun-compat-session.md
  • docs/agents/runs/issue-101-session.md
  • docs/agents/runs/issue-102-loaded-suite-followup-session.md
  • docs/agents/runs/issue-102-session.md
  • docs/agents/runs/issue-103-session.md
  • docs/agents/runs/issue-104-session.md
  • docs/agents/runs/issue-105-session.md
  • docs/agents/runs/issue-106-review-packet.md
  • docs/agents/runs/issue-106-session.md
  • docs/agents/runs/issue-107-session.md
  • examples/EXAMPLE_STANDARDIZATION.md
  • examples/README.md
  • examples/nip19/README.md
  • examples/nip44/nip44-demo.js
  • package.json
  • scripts/verify-commands.js
  • src/entries/index.web.ts
  • src/index.ts
  • src/nip01/relay.ts
  • src/nip02/README.md
  • src/nip02/index.ts
  • src/nip47/README.md
  • src/nip47/client.ts
  • src/nip47/service.ts
  • src/nip47/types.ts
  • src/nip57/README.md
  • src/nip57/client.ts
  • src/nip57/diagnostics.ts
  • src/nip57/index.ts
  • src/nip57/utils.ts
  • src/nip86/index.ts
  • src/shims/nip04-stub.ts
  • src/utils/ephemeral-relay.ts
  • src/utils/logger.ts
  • src/utils/websocket.ts
  • tests/README.md
  • tests/TEST_STANDARDIZATION.md
  • tests/entries/web-exports.test.ts
  • tests/nip01/relay/websocket-implementation.test.ts
  • tests/nip02/nip02.test.ts
  • tests/nip57/client.test.ts
  • tests/nip86/nip86.test.ts
  • tests/scripts/verify-commands.test.ts
  • tests/security/security-limits-public.test.ts
  • tests/types/logger-compatibility.ts
  • tests/utils/ephemeral-relay-close-ordering.test.ts
  • tests/utils/ephemeral-relay-lifecycle.test.ts
  • tests/utils/security-validator.test.ts
  • tests/utils/websocket.test.ts
💤 Files with no reviewable changes (2)
  • src/shims/nip04-stub.ts
  • examples/nip44/nip44-demo.js

- Feature branch: `feature/cleanup-runtime-debt`
- Human owner: plebdev
- Started: 2026-07-14
- Current status: implementation, integrated verification, and local final review complete; non-draft staging PR #108 open for hosted review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Refresh the current PR metadata.

The active review is PR #109 (stagingmain), but these lines describe PR #108 as the current open review. Update the references or explicitly label PR #108 as historical.

Also applies to: 32-32

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/agents/runs/feature-cleanup-runtime-debt-ledger.md` at line 12, Update
the PR metadata references in the document’s current-status entries to identify
PR `#109` as the active staging-to-main review. Explicitly label PR `#108` as
historical wherever it remains mentioned, including the additional referenced
line.

Comment thread src/nip57/client.ts
Comment on lines +144 to +151
/** Collect matching zap receipts until the first EOSE or the legacy timeout. */
private collectZapReceipts(filter: Filter): Promise<NostrEvent[]> {
return new Promise((resolve, reject) => {
const events: NostrEvent[] = [];
const activeSubscriptionIds = new Set<string>();
let isSettled = false;
let subscribeComplete = false;
let reachedEoseSynchronously = false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wait for EOSE from every subscribed relay.

Nostr.subscribe() forwards EOSE once per relay, but handleEose() settles on the first callback. A faster relay therefore closes every subscription before slower relays finish; synchronous EOSE can also suppress events from relays subscribed later in the same call. Track completed relays through subscribeDetailed() and settle only after every returned subscription reaches EOSE or the timeout fires. (github.com)

Add a staggered two-relay regression test. As per coding guidelines, require comprehensive tests for new features and use the ephemeral relay for relay-dependent tests.

Also applies to: 187-201, 229-236

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/nip57/client.ts` around lines 144 - 151, Update collectZapReceipts to use
subscribeDetailed() and track EOSE completion for every returned relay
subscription, settling only once all subscribed relays have completed or the
legacy timeout fires; preserve events from relays whose EOSE arrives later,
including synchronous EOSE during subscription setup. Add a staggered two-relay
regression test using the ephemeral relay setup to verify both relays’ events
are collected before resolution.

Sources: Coding guidelines, MCP tools

Comment thread src/nip57/client.ts
Comment on lines +147 to +148
const events: NostrEvent[] = [];
const activeSubscriptionIds = new Set<string>();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Deduplicate receipts by event ID.

The same signed receipt received from multiple relays is appended repeatedly, causing getTotalZapsReceived() and event statistics to double-count it. Collect into a Map<string, NostrEvent> keyed by event.id, then resolve its values. Nostr forwards events independently from each relay. (github.com)

Add a multi-relay duplicate-receipt test. As per coding guidelines, require comprehensive tests for new features.

Also applies to: 184-184, 198-200

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/nip57/client.ts` around lines 147 - 148, Replace the receipt collection
array in the subscription flow with a Map<string, NostrEvent> keyed by event.id,
updating entries as receipts arrive and resolving the map values before
calculating totals or event statistics. Preserve activeSubscriptionIds behavior,
and add a multi-relay test confirming identical receipts are counted only once.

Sources: Coding guidelines, MCP tools

Comment on lines +134 to +146
constructor(
port: number,
purgeIntervalOrOptions?: number | NostrRelayOptions,
) {
const options =
typeof purgeIntervalOrOptions === "number"
? { purgeInterval: purgeIntervalOrOptions }
: (purgeIntervalOrOptions ?? {});

this._cache = [];
this._emitter = new EventEmitter();
this._port = port;
this._purge = purge_ival ?? null;
this._purge = options.purgeInterval ?? null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 '\bpurgeInterval\b|\b_purge\b' src tests README.md

Repository: AustinKelsay/snstr

Length of output: 3517


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,260p' src/utils/ephemeral-relay.ts
printf '\n---\n'
rg -n 'purgeInterval|setInterval|clearInterval|maybeUnref|normalize|validate|finite|positive' src/utils/ephemeral-relay.ts tests/utils/ephemeral-relay-lifecycle.test.ts

Repository: AustinKelsay/snstr

Length of output: 10164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path('src/utils/ephemeral-relay.ts')
text = p.read_text()
for term in ['purgeInterval', 'setInterval', 'clearInterval', 'maybeUnref', 'validate']:
    print(f'== {term} ==')
    for i, line in enumerate(text.splitlines(), 1):
        if term in line:
            print(f'{i}: {line}')
PY

Repository: AustinKelsay/snstr

Length of output: 1902


🏁 Script executed:

node - <<'JS'
const cases = [0, -1, -100, NaN, Infinity, -Infinity, 0.01, 1];
for (const d of cases) {
  const t = setInterval(() => {}, d);
  // timer internals are not public API, but this is a read-only probe.
  console.log(String(d).padEnd(10), '->', t._idleTimeout);
  clearInterval(t);
}
JS

Repository: AustinKelsay/snstr

Length of output: 641


🏁 Script executed:

node - <<'JS'
const cases = [0, -1, -100, NaN, Infinity, -Infinity, 0.01, 1];
for (const d of cases) {
  const t = setInterval(() => {}, d);
  console.log(JSON.stringify({ delay: d, idleTimeout: t._idleTimeout }));
  clearInterval(t);
}
JS

Repository: AustinKelsay/snstr

Length of output: 753


🌐 Web query:

Node.js setInterval delay NaN negative Infinity minimum 1 ms documentation

💡 Result:

In Node.js, the setInterval function handles invalid or out-of-range delay values by automatically coercing them to a default value of 1 millisecond [1][2]. Specifically, according to official Node.js documentation, when the delay argument is: * Less than 1 millisecond [1][3] * NaN (Not-a-Number) [1][2] * Larger than 2,147,483,647 milliseconds (the maximum 32-bit signed integer value) [1][4] The delay is set to 1 millisecond [1][5]. Additionally, any non-integer delay values provided are truncated to their integer part [1][3]. While this is the current behavior, Node.js has introduced documentation-only deprecation warnings to discourage the use of NaN or negative numbers as delay values, as these are considered invalid input [6]. It is recommended to always use positive integer values for timer delays to ensure predictable behavior [1][2].

Citations:


Validate purgeInterval before scheduling it
src/utils/ephemeral-relay.ts:136-146setInterval coerces 0, negative, NaN, and Infinity to a 1ms timer, so these inputs still create an extremely aggressive purge loop instead of disabling purge. Reject non-finite/non-positive values, or make 0 an explicit disabled sentinel for both constructor forms.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/ephemeral-relay.ts` around lines 134 - 146, The constructor’s purge
interval handling must disable scheduling for zero, negative, NaN, and Infinity
values. Update the options normalization and _purge assignment in the
constructor to validate purgeInterval consistently for both numeric and
NostrRelayOptions inputs, preserving only finite positive intervals and treating
invalid or zero values as disabled.

Comment on lines 177 to 184
async start() {
if (this._closePromise) {
await this._closePromise;
}

if (this._wss || this._inMemoryServer) {
return this;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Cache and return the in-flight startup promise.

The first call assigns _wss before listening; a concurrent start() then returns success at Line 182 even though binding may still fail. Track _startPromise like _closePromise and clear it after settlement.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/ephemeral-relay.ts` around lines 177 - 184, Update the start()
method to cache its in-flight startup operation in a _startPromise, making
concurrent calls await and reuse that promise before returning the relay. Clear
_startPromise after the startup promise settles, while preserving the existing
_closePromise handling and already-started checks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant