diff --git a/NIP_INVENTORY.md b/NIP_INVENTORY.md index f2be2338..a9bde345 100644 --- a/NIP_INVENTORY.md +++ b/NIP_INVENTORY.md @@ -17,7 +17,7 @@ | **NIP-56** | Reporting | `js/services/moderationService.js`, `docs/nips/56.md` | Compliant | `buildReportEvent` implements Kind 1984. | | **NIP-57** | Zaps | `js/payments/zapRequests.js`, `js/payments/zapSplit.js` | Compliant | Implements Kind 9734/9735 flow. | | **NIP-59** | Gift Wrap | `js/nostr/client.js` | Compliant | Implemented as part of NIP-17. | -| **NIP-65** | Relay List Metadata | `js/relayManager.js` | Partial | Code supports Kind 10002. Test `nip65_compliance.test.mjs` is missing. | +| **NIP-65** | Relay List Metadata | `js/relayManager.js` | Compliant | Code supports Kind 10002. Verified by `tests/nostr-specs/nip65_compliance.test.mjs`. | | **NIP-71** | Video Events | `js/nostr/nip71.js` | Partial | Kinds 21/22 supported in schema, but Kind 30078 is primary. Verified tag generation in `tests/nostr-specs/kind30078.test.mjs`. | | **NIP-78** | Application-specific data | `js/services/hashtagPreferencesService.js` | Compliant | Used for hashtag preferences (Kind 30015). | | **NIP-94** | File Metadata | `js/nostr/videoPublisher.js` | Compliant | `buildVideoMirrorEvent` implements Kind 1063. | diff --git a/context/CONTEXT_2026-02-18_17-00-00.md b/context/CONTEXT_2026-02-18_17-00-00.md new file mode 100644 index 00000000..8511619b --- /dev/null +++ b/context/CONTEXT_2026-02-18_17-00-00.md @@ -0,0 +1,14 @@ +# Context: NIP Research Agent Run (2026-02-18) + +## Goal +Execute the daily `nip-research-agent` task to research NIPs, verify compliance, and update the inventory. + +## Scope +- Focus on NIP-65 / Kind 10002 compliance verification. +- Implement missing tests for `relayManager.js`. +- Update `NIP_INVENTORY.md`. + +## Definition of Done +- `tests/nostr-specs/nip65_compliance.test.mjs` created and passing. +- `NIP_INVENTORY.md` updated. +- Completion log submitted. diff --git a/decisions/DECISIONS_2026-02-18_17-00-00.md b/decisions/DECISIONS_2026-02-18_17-00-00.md new file mode 100644 index 00000000..06ab1535 --- /dev/null +++ b/decisions/DECISIONS_2026-02-18_17-00-00.md @@ -0,0 +1,4 @@ +# Decisions: NIP Research Agent (2026-02-18) + +- **Prioritize NIP-65**: The inventory lists NIP-65 as "Partial" due to missing tests. As a P0 item (Relay Lists), this is the highest priority gap to close. +- **Test Strategy**: Create a new test file `tests/nostr-specs/nip65_compliance.test.mjs` that mocks `nostrClient` to test `relayManager` in isolation. diff --git a/docs/agents/task-logs/daily/2026-02-18_17-00-00_nip-research-agent_completed.md b/docs/agents/task-logs/daily/2026-02-18_17-00-00_nip-research-agent_completed.md new file mode 100644 index 00000000..e070b59a --- /dev/null +++ b/docs/agents/task-logs/daily/2026-02-18_17-00-00_nip-research-agent_completed.md @@ -0,0 +1,14 @@ +# Daily Task: nip-research-agent + +- **Date**: 2026-02-18 +- **Agent**: nip-research-agent +- **Status**: Completed +- **Output**: + - Created `tests/nostr-specs/nip65_compliance.test.mjs` (verified NIP-65/Kind 10002). + - Updated `NIP_INVENTORY.md` (NIP-65 -> Compliant). + - Created `nip-report-2026-02-18.md`. + - Updated agent state files in `context/`, `todo/`, `decisions/`, `test_logs/`. + +## Verification +- Ran `npm test -- tests/nostr-specs/nip65_compliance.test.mjs`: PASSED. +- Ran `npm run lint`: PASSED. diff --git a/nip-report-2026-02-18.md b/nip-report-2026-02-18.md new file mode 100644 index 00000000..01ff5027 --- /dev/null +++ b/nip-report-2026-02-18.md @@ -0,0 +1,18 @@ +# NIP Compliance Report (2026-02-18) + +## Executive Summary +This run focused on closing the compliance gap for **NIP-65 (Relay List Metadata)**, identified as a P0 priority. A new regression test suite `tests/nostr-specs/nip65_compliance.test.mjs` was created to verify `relayManager.js` behavior against Kind 10002 specifications. The test confirms correct parsing of relay tags (including read/write markers) and correct event building. + +## Changes +- **NIP-65**: Status upgraded from `Partial` to `Compliant`. +- **Tests**: Added `tests/nostr-specs/nip65_compliance.test.mjs`. + +## Compliance Status (Snapshot) +- **Compliant**: NIP-01, NIP-04, NIP-07, NIP-10, NIP-17, NIP-19, NIP-33, NIP-44, NIP-46, NIP-51, NIP-56, NIP-57, NIP-59, NIP-65, NIP-78, NIP-94, NIP-98, NIP-25, NIP-47. +- **Partial**: NIP-09 (Event Deletion - incomplete Kind 5), NIP-71 (Video Events - mixed Kind 30078/21/22), NIP-96 (HTTP File Storage). +- **Unknown**: NIP-21 (URI Scheme), NIP-42 (Auth). + +## Next Steps +- **NIP-42 (Auth)**: Verify authentication flow (RELAY AUTH). +- **NIP-96**: Investigate HTTP File Storage integration status. +- **NIP-71**: Clarify path forward for standard vs legacy video kinds. diff --git a/test_logs/TEST_LOG_2026-02-18_17-00-00.md b/test_logs/TEST_LOG_2026-02-18_17-00-00.md new file mode 100644 index 00000000..4b8f41ea --- /dev/null +++ b/test_logs/TEST_LOG_2026-02-18_17-00-00.md @@ -0,0 +1,13 @@ +# Test Log: NIP Research Agent (2026-02-18) + +## Execution +Ran `node scripts/run-unit-tests.mjs tests/nostr-specs/nip65_compliance.test.mjs`. + +## Results +- `tests/nostr-specs/nip65_compliance.test.mjs` PASSED. + - Verified NIP-65 parsing (relay list events). + - Verified NIP-65 event building (Kind 10002). + +## Notes +- Mocked `nostrClient` and `setActiveSigner` to simulate environment. +- Used `local` signer type to bypass extension permissions in test. diff --git a/tests/nostr-specs/nip65_compliance.test.mjs b/tests/nostr-specs/nip65_compliance.test.mjs new file mode 100644 index 00000000..25dd30c1 --- /dev/null +++ b/tests/nostr-specs/nip65_compliance.test.mjs @@ -0,0 +1,135 @@ +import { test, describe, it, before, after, beforeEach } from 'node:test'; +import assert from 'node:assert'; +import { relayManager } from '../../js/relayManager.js'; +import { nostrClient, setActiveSigner } from '../../js/nostrClientFacade.js'; + +// Mock browser globals +if (typeof globalThis.window === 'undefined') { + globalThis.window = { + location: { protocol: 'https:' }, + localStorage: { + getItem: () => null, + setItem: () => {}, + } + }; +} + +describe('NIP-65 / Kind 10002 Compliance', () => { + let originalPool; + let originalSigner; + + before(() => { + // Save original state + originalPool = nostrClient.pool; + }); + + after(() => { + // Restore original state + nostrClient.pool = originalPool; + }); + + beforeEach(() => { + relayManager.reset(); + }); + + it('should parse NIP-65 relay list events correctly', async () => { + const pubkey = '00'.repeat(32); + const mockEvent = { + kind: 10002, + pubkey: pubkey, + created_at: 1000, + tags: [ + ['r', 'wss://relay.example.com'], + ['r', 'wss://read.example.com', 'read'], + ['r', 'wss://write.example.com', 'write'], + ], + content: '' + }; + + // Mock pool.list to return our event + nostrClient.pool = { + list: async () => [mockEvent], + publish: async () => {}, + }; + + const result = await relayManager.loadRelayList(pubkey); + assert.strictEqual(result.ok, true); + assert.strictEqual(result.source, 'event'); + + const entries = relayManager.getEntries(); + assert.strictEqual(entries.length, 3); + + const both = entries.find(e => e.url === 'wss://relay.example.com'); + assert.ok(both); + assert.strictEqual(both.mode, 'both'); + assert.strictEqual(both.read, true); + assert.strictEqual(both.write, true); + + const read = entries.find(e => e.url === 'wss://read.example.com'); + assert.ok(read); + assert.strictEqual(read.mode, 'read'); + assert.strictEqual(read.read, true); + assert.strictEqual(read.write, false); + + const write = entries.find(e => e.url === 'wss://write.example.com'); + assert.ok(write); + assert.strictEqual(write.mode, 'write'); + assert.strictEqual(write.read, false); + assert.strictEqual(write.write, true); + }); + + it('should build a valid Kind 10002 event for publishing', async () => { + const pubkey = '00'.repeat(32); + + // Set up relays + relayManager.setEntries([ + { url: 'wss://relay.example.com', mode: 'both' }, + { url: 'wss://read.example.com', mode: 'read' }, + ]); + + // Mock signer + // Use 'local' type to bypass permission check in relayManager + const mockSigner = { + type: 'local', + signEvent: async (evt) => { + return { ...evt, id: 'mock-id', sig: 'mock-sig', pubkey }; + } + }; + + setActiveSigner(mockSigner); + + // Mock pool.publish to capture the event + let capturedEvent = null; + nostrClient.pool = { + publish: (urls, event) => { + capturedEvent = event; + // Return a promise-like object that resolves + return { + on: (evt, cb) => { if (evt === 'ok') cb(); }, + then: (cb) => { cb(); } + }; + } + }; + + await relayManager.publishRelayList(pubkey); + + assert.ok(capturedEvent); + assert.strictEqual(capturedEvent.kind, 10002); + assert.strictEqual(capturedEvent.pubkey, pubkey); + + // Check tags + const rTags = capturedEvent.tags.filter(t => t[0] === 'r'); + assert.strictEqual(rTags.length, 2); + + // Note: relayManager normalizes URLs (no trailing slash if empty path) + const bothTag = rTags.find(t => t[1] === 'wss://relay.example.com'); + assert.ok(bothTag, 'Missing tag for wss://relay.example.com'); + // 'both' usually has no marker or explicit? relayManager uses parseRelayTags/buildRelayListEvent logic. + // buildRelayListEvent: if mode is read -> read, write -> write, else -> no marker (both) + assert.strictEqual(bothTag.length, 2); // ['r', 'url'] + + const readTag = rTags.find(t => t[1] === 'wss://read.example.com'); + assert.ok(readTag, 'Missing tag for wss://read.example.com'); + assert.strictEqual(readTag[2], 'read'); + }); +}); diff --git a/todo/TODO_2026-02-18_17-00-00.md b/todo/TODO_2026-02-18_17-00-00.md new file mode 100644 index 00000000..f3efb583 --- /dev/null +++ b/todo/TODO_2026-02-18_17-00-00.md @@ -0,0 +1,8 @@ +# TODO: NIP Research Agent (2026-02-18) + +- [ ] Create `tests/nostr-specs/nip65_compliance.test.mjs` +- [ ] Run `npm test -- tests/nostr-specs/nip65_compliance.test.mjs` +- [ ] Update `NIP_INVENTORY.md` (mark NIP-65 as Compliant) +- [ ] Generate `nip-report-2026-02-18.md` +- [ ] Run `npm run lint` +- [ ] Submit completion log