Initial implementation: validation pipeline, store, and dashboard - #1
Initial implementation: validation pipeline, store, and dashboard#1alltheseas wants to merge 6 commits into
Conversation
Minimal test relay that accepts EVENT (with base field validation), REQ (immediate EOSE, no stored events), and CLOSE. Serves NIP-11 relay info document. Verified working with nak and raw WebSocket client. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Integrate the schemata-codegen generated validators (179 kinds, 133 with tag/content constraints) into the event handling pipeline: - Verify event id matches SHA-256 of NIP-01 serialized form - Verify schnorr signature via @noble/curves - Run validateEvent() for content constraints and validateKindTags() for tag structure constraints - Classify each kind into three tiers: validated (has schema constraints), base-only (known but no constraints), unknown - Accept schema-invalid events (true) with error details in OK message so clients continue working; reject only crypto failures Vendored files from schemata-codegen: validators.ts (2907 lines, 139 kind-specific tag validators) and kind-registry.ts (177 kind metadata entries with names and categories). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Persist every validation result to SQLite (better-sqlite3, WAL mode) with indexes on pubkey, kind, and received_at for dashboard queries. Schema: event_id (PK), pubkey, kind, kind_name, created_at, received_at, tier, valid, errors (JSON), client_tag. REST API endpoints: - GET /api/results?pubkey=&kind=&limit=&offset= — query results - GET /api/summary?pubkey= — aggregate pass/fail by tier and kind - GET /api/pubkeys — list pubkeys with event counts NIP-89 client tag extracted from event tags and stored for attribution in the dashboard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Self-contained HTML dashboard served at the relay's HTTP root. Connects to /api/events via Server-Sent Events for live streaming of validation results as events arrive. Features: - Summary stats: total events, passed, failed, kinds seen - Tier breakdown badges (validated/base-only/unknown with counts) - Filter by pubkey, validation tier, and pass/fail status - Per-event detail: kind name, event id, pubkey, timestamp, client tag, and inline schema error display - Light/dark theme via prefers-color-scheme - Responsive layout, reduced-motion support Bumps version to 0.3.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Documents the validation pipeline, three-tier classification, dashboard features, REST API, configuration, and how test-relay relates to schemata, schemata-codegen, nostr-test-vectors, and sherlock within the nostrability ecosystem. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR adds a standalone Nostr test-relay: HTTP + WebSocket servers, a four-stage event validation pipeline, SQLite persistence for validation results, a live SSE-backed dashboard, and supporting artifacts (kind registry, TypeScript config, package manifest, README). Changes
Sequence DiagramsequenceDiagram
participant Client as Client
participant WS as WebSocket Server
participant Validator as Event Validator
participant DB as SQLite DB
participant SSE as SSE Broadcaster
participant Dashboard as Dashboard (Browser)
Client->>WS: Send EVENT (WS message)
WS->>Validator: Parse message, extract event
Validator->>Validator: Base NIP-01 checks
Validator->>Validator: Compute SHA-256 ID and compare
Validator->>Validator: Verify schnorr signature
Validator->>Validator: Schema validate & determine tier
Validator->>DB: Persist ValidationResult
DB->>SSE: Notify new result
SSE->>Dashboard: Push event (SSE)
WS->>Client: Reply with OK / NOTICE (validation outcome)
Dashboard->>Dashboard: Update UI / filters / stats
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review please |
|
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (6)
src/index.ts (2)
77-82: SSE write errors are silently ignored.If a client disconnects unexpectedly or the write buffer is full,
res.write()can fail. While thecloseevent handler removes clients, there's no error handling for write failures which could cause unhandled exceptions in edge cases.💡 Add defensive error handling
function broadcastSSE(result: ValidationResult): void { const data = `data: ${JSON.stringify(result)}\n\n`; for (const res of sseClients) { - res.write(data); + try { + res.write(data); + } catch { + sseClients.delete(res); + } } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.ts` around lines 77 - 82, The broadcastSSE function currently calls res.write(...) for each client in sseClients without handling write failures; wrap each write in a try/catch (or check the return value) and on error remove the failing client from sseClients and properly close/destroy the response stream (and optionally log the error). Also add an 'error' event listener to each SSE response when clients are added so write-time errors are handled centrally; update the code that manages sseClients (where clients are pushed) and the broadcastSSE function to reference those listeners and cleanup logic.
494-506: Startup logging exposes binding address — consider operational note.The server binds to all interfaces by default when no host is specified in
listen(). This is fine for local development but should be documented for production deployment scenarios.For production deployments, consider:
- Binding to specific interface:
httpServer.listen(PORT, '127.0.0.1', ...)- Adding environment variable for bind address
- Documenting reverse proxy setup in README
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/index.ts` around lines 494 - 506, Change the httpServer.listen call to accept a configurable bind address instead of implicitly binding to all interfaces: read a BIND_ADDRESS (or similar) env var with a safe default (e.g., '0.0.0.0' or '127.0.0.1' depending on desired default), pass that value as the host argument to httpServer.listen(PORT, bindAddress, ...), and update the console output to show the effective bind address used; this uses the existing httpServer.listen and PORT symbols so you only need to replace the current listen invocation and logs to reference the chosen bindAddress and document the new env var in README or deployment notes.src/dashboard.ts (2)
228-228: External Google Fonts dependency has privacy implications.Loading fonts from Google Fonts sends user IP addresses to Google. For a developer testing tool this may be acceptable, but consider noting this in documentation or providing a self-hosted alternative for privacy-conscious users.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/dashboard.ts` at line 228, The HTML in src/dashboard.ts currently loads Google Fonts via an external <link> tag which leaks user IPs; remove or make this optional and provide a self-hosted/fallback strategy instead: replace the hardcoded external <link> usage with a configurable option (e.g., a flag or env var) that either serves local font files bundled with the app or falls back to system fonts, and update documentation to mention the privacy trade-off and how to enable the self-hosted fonts; search for the Google Fonts <link> tag in src/dashboard.ts to locate the change point and implement the config-driven loading and doc note.
303-306: SSE reconnect uses fixed delay without exponential backoff.A fixed 3-second reconnect could cause connection storms if the server is under load or temporarily unavailable. Consider exponential backoff with jitter and a maximum retry limit.
💡 Add exponential backoff
let reconnectAttempts = 0; const MAX_RECONNECT_DELAY = 30000; function connectSSE() { const es = new EventSource('/api/events'); es.onopen = () => { reconnectAttempts = 0; }; es.onmessage = (e) => { /* ... existing code ... */ }; es.onerror = () => { es.close(); const delay = Math.min(1000 * Math.pow(2, reconnectAttempts) + Math.random() * 1000, MAX_RECONNECT_DELAY); reconnectAttempts++; setTimeout(connectSSE, delay); }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/dashboard.ts` around lines 303 - 306, The current es.onerror handler in connectSSE uses a fixed 3s retry; replace this with an exponential backoff with jitter and a cap: introduce a reconnectAttempts counter (reset to 0 in es.onopen), define a MAX_RECONNECT_DELAY (e.g., 30000ms) and compute delay = min(base * 2^reconnectAttempts + randomJitter, MAX_RECONNECT_DELAY), increment reconnectAttempts on each error, then setTimeout(connectSSE, delay) after es.close(); optionally enforce a maxAttempts limit to stop retrying. Ensure you update the es.onopen and es.onerror handlers in the connectSSE function accordingly (use the symbols reconnectAttempts, MAX_RECONNECT_DELAY, connectSSE, es.onerror, es.onopen).src/store.ts (1)
33-36: No guard against double initialization.If
initDB()is called multiple times, it will create a new database connection without closing the previous one, potentially causing resource leaks. While unlikely in current usage, adding a guard improves robustness.💡 Add initialization guard
let db: Database.Database; export function initDB(): Database.Database { + if (db) return db; db = new Database(DB_PATH);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/store.ts` around lines 33 - 36, The initDB function currently creates a new Database instance every call (using the db variable and Database.Database), risking resource leaks; add a guard in initDB that checks the module-level db variable and if already initialized either returns the existing db or first closes it before creating a new one, ensuring you call the Database.close() (or appropriate close method) on the old instance if you choose to recreate; update initDB to return the existing Database.Database when present and ensure any caller that expects reinitialization can explicitly call a new teardown/close function you add.package.json (1)
10-10: Dev script has a potential race condition and cross-platform issue.The
tsc --watch & node --watch dist/index.jspattern:
- Runs
tsc --watchin background, then immediately startsnode --watch- On first run,
dist/index.jsmay not exist yet (TypeScript hasn't finished compiling)- The
&operator is shell-specific and won't work on Windows cmdConsider using a tool like
concurrentlyornpm-run-allfor reliable parallel execution with proper startup sequencing.💡 Suggested alternative using concurrently
"scripts": { "build": "tsc", "start": "node dist/index.js", - "dev": "tsc --watch & node --watch dist/index.js" + "dev": "npm run build && concurrently \"tsc --watch\" \"node --watch dist/index.js\"" },And add to devDependencies:
"concurrently": "^8.0.0"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` at line 10, The dev script "tsc --watch & node --watch dist/index.js" causes a race on initial compile and is shell-specific; replace it with a cross-platform parallel runner (e.g., add "concurrently" to devDependencies) and change the "dev" script to run the TypeScript compiler and the Node process via that tool so node only starts/restarts after files in dist exist (or use nodemon via the runner to watch dist). Update package.json's dev script and add the concurrently dependency, referencing the existing "tsc --watch" and "node --watch dist/index.js" commands when constructing the new concurrently-based script.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@README.md`:
- Around line 59-64: The fenced code block listing the API endpoints (the lines
starting with "GET /api/results...", "GET /api/summary...", "GET /api/pubkeys",
and "GET /api/events") is missing a language specifier; update the opening fence
from ``` to a specified language such as ```text or ```http so static analysis
passes and the block is properly highlighted.
- Around line 96-117: The fenced code block containing the architecture diagram
uses plain triple backticks without a language specifier; update the opening
fence from ``` to ```text to mark it as a text block (the block that starts with
the diagram lines "Client (any Nostr app)" and the EVENT line) so markdown
renderers treat it as plain text and preserve formatting.
In `@src/dashboard.ts`:
- Around line 383-386: The code building the error HTML assigns r.errors entries
to errors but escapes only e.message; escape e.path as well by passing it
through the existing escapeHtml helper before concatenation so the line that
constructs errors uses escapeHtml(e.path) instead of e.path (refer to r.errors,
e.path, escapeHtml and the errors variable / "<div class=\"event-errors\">").
This ensures both path and message are HTML-escaped and prevents XSS when
rendering the event-errors block.
In `@src/index.ts`:
- Around line 132-146: In handleApiResults, the numeric query params (kind,
limit, offset) must be validated for NaN before calling getResults; after
parsing each with Number(), check isNaN(...) and if any are invalid return a 400
jsonResponse with an error message (so getResults only ever receives valid
numbers or undefined for kind), otherwise use the parsed values (with existing
defaults for limit/offset) when calling getResults; update handleApiResults to
perform these isNaN checks and short-circuit with jsonResponse on error.
In `@src/kind-registry.ts`:
- Around line 284-292: Kind 777 (“nipless”, name "Spell — portable REQ filter
(nipless)") was generated with hasConditionals: false but category:
"conditional", so update the upstream schemata so the generated output matches
the other conditional kinds: change the schemata definition for kind 777 to mark
it as conditional (set hasConditionals=true or equivalent flag in the source
schema) or adjust the category mapping logic so that a conditional kind yields
category "conditional" only when hasConditionals is true; then re-run
`@nostrability/schemata-codegen` to regenerate the file (look for the schema entry
for kind 777 or the mapping function that derives category/hasConditionals) and
run tests to ensure consistency with other "nipless" conditional kinds.
In `@src/store.ts`:
- Around line 181-194: The rowToResult function uses JSON.parse on row.errors
which can throw on malformed JSON; wrap the parsing of row.errors in a defensive
try/catch inside rowToResult (or a small helper) so that if JSON.parse fails you
return a safe default (e.g., an empty array or null) and do not let the
exception bubble up; include a descriptive debug/warn log mentioning
event_id/pubkey and the parse error to aid troubleshooting while still returning
a valid ValidationResult with errors set to the safe default.
---
Nitpick comments:
In `@package.json`:
- Line 10: The dev script "tsc --watch & node --watch dist/index.js" causes a
race on initial compile and is shell-specific; replace it with a cross-platform
parallel runner (e.g., add "concurrently" to devDependencies) and change the
"dev" script to run the TypeScript compiler and the Node process via that tool
so node only starts/restarts after files in dist exist (or use nodemon via the
runner to watch dist). Update package.json's dev script and add the concurrently
dependency, referencing the existing "tsc --watch" and "node --watch
dist/index.js" commands when constructing the new concurrently-based script.
In `@src/dashboard.ts`:
- Line 228: The HTML in src/dashboard.ts currently loads Google Fonts via an
external <link> tag which leaks user IPs; remove or make this optional and
provide a self-hosted/fallback strategy instead: replace the hardcoded external
<link> usage with a configurable option (e.g., a flag or env var) that either
serves local font files bundled with the app or falls back to system fonts, and
update documentation to mention the privacy trade-off and how to enable the
self-hosted fonts; search for the Google Fonts <link> tag in src/dashboard.ts to
locate the change point and implement the config-driven loading and doc note.
- Around line 303-306: The current es.onerror handler in connectSSE uses a fixed
3s retry; replace this with an exponential backoff with jitter and a cap:
introduce a reconnectAttempts counter (reset to 0 in es.onopen), define a
MAX_RECONNECT_DELAY (e.g., 30000ms) and compute delay = min(base *
2^reconnectAttempts + randomJitter, MAX_RECONNECT_DELAY), increment
reconnectAttempts on each error, then setTimeout(connectSSE, delay) after
es.close(); optionally enforce a maxAttempts limit to stop retrying. Ensure you
update the es.onopen and es.onerror handlers in the connectSSE function
accordingly (use the symbols reconnectAttempts, MAX_RECONNECT_DELAY, connectSSE,
es.onerror, es.onopen).
In `@src/index.ts`:
- Around line 77-82: The broadcastSSE function currently calls res.write(...)
for each client in sseClients without handling write failures; wrap each write
in a try/catch (or check the return value) and on error remove the failing
client from sseClients and properly close/destroy the response stream (and
optionally log the error). Also add an 'error' event listener to each SSE
response when clients are added so write-time errors are handled centrally;
update the code that manages sseClients (where clients are pushed) and the
broadcastSSE function to reference those listeners and cleanup logic.
- Around line 494-506: Change the httpServer.listen call to accept a
configurable bind address instead of implicitly binding to all interfaces: read
a BIND_ADDRESS (or similar) env var with a safe default (e.g., '0.0.0.0' or
'127.0.0.1' depending on desired default), pass that value as the host argument
to httpServer.listen(PORT, bindAddress, ...), and update the console output to
show the effective bind address used; this uses the existing httpServer.listen
and PORT symbols so you only need to replace the current listen invocation and
logs to reference the chosen bindAddress and document the new env var in README
or deployment notes.
In `@src/store.ts`:
- Around line 33-36: The initDB function currently creates a new Database
instance every call (using the db variable and Database.Database), risking
resource leaks; add a guard in initDB that checks the module-level db variable
and if already initialized either returns the existing db or first closes it
before creating a new one, ensuring you call the Database.close() (or
appropriate close method) on the old instance if you choose to recreate; update
initDB to return the existing Database.Database when present and ensure any
caller that expects reinitialization can explicitly call a new teardown/close
function you add.
🪄 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: dd870203-1be8-417e-b5dd-d9fbcd28c84a
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (8)
README.mdpackage.jsonsrc/dashboard.tssrc/index.tssrc/kind-registry.tssrc/schemata-validators.tssrc/store.tstsconfig.json
| 777: { | ||
| kind: 777, | ||
| nip: "nipless", | ||
| name: "Spell — portable REQ filter (nipless)", | ||
| description: "Spell — portable REQ filter (Grimoire/nak)", | ||
| requiredTags: ["cmd"], | ||
| hasConditionals: false, | ||
| category: "conditional", | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if other kinds have similar inconsistency
rg -n 'hasConditionals: false' src/kind-registry.ts | head -20
rg -n 'category: "conditional"' src/kind-registry.ts | head -20Repository: nostrability/test-relay
Length of output: 1357
🏁 Script executed:
#!/bin/bash
# Extract context around each conditional category to see the full kind entry
# Get line numbers for conditional category
conditional_lines=$(rg -n 'category: "conditional"' src/kind-registry.ts | cut -d: -f1)
# For each line, show context to identify kind number and hasConditionals value
for line_num in $conditional_lines; do
# Show the kind entry (usually 10 lines)
start=$((line_num - 8))
sed -n "${start},${line_num}p" src/kind-registry.ts | head -10
echo "---"
done | head -100Repository: nostrability/test-relay
Length of output: 2578
🏁 Script executed:
#!/bin/bash
# Check for any comments or documentation about kind 777 or Spell
rg -B5 -A5 'kind: 777' src/kind-registry.ts
# Check for codegen markers or comments in the file
head -50 src/kind-registry.ts | grep -i 'auto\|generated\|codegen'
# Check if there are other "nipless" kinds
rg -n 'nipless' src/kind-registry.tsRepository: nostrability/test-relay
Length of output: 1636
🏁 Script executed:
#!/bin/bash
# Get all nipless kinds and check their hasConditionals and category values
rg -B6 'nipless' src/kind-registry.ts | grep -E 'kind: [0-9]+|hasConditionals:|category:' | paste - - - | head -20Repository: nostrability/test-relay
Length of output: 110
🏁 Script executed:
#!/bin/bash
# Extract all nipless kind entries with their properties
python3 << 'EOF'
import re
with open('src/kind-registry.ts', 'r') as f:
content = f.read()
# Find all kind entries that contain "nipless"
pattern = r'(\d+):\s*\{[^}]*nip:\s*"nipless"[^}]*hasConditionals:\s*(true|false)[^}]*category:\s*"([^"]+)"'
matches = re.findall(pattern, content, re.DOTALL)
print("Kind | hasConditionals | category")
print("-----|-----------------|----------")
for kind, has_cond, category in matches:
print(f"{kind:5} | {has_cond:15} | {category}")
EOFRepository: nostrability/test-relay
Length of output: 453
Fix upstream codegen for kind 777: inconsistent hasConditionals and category fields.
Kind 777 has hasConditionals: false but category: "conditional", conflicting with all other conditional kinds (which have hasConditionals: true). All other "nipless" kinds consistently pair their categories with their hasConditionals value. Since this is auto-generated by @nostrability/schemata-codegen, investigate and fix the upstream schemata definition.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/kind-registry.ts` around lines 284 - 292, Kind 777 (“nipless”, name
"Spell — portable REQ filter (nipless)") was generated with hasConditionals:
false but category: "conditional", so update the upstream schemata so the
generated output matches the other conditional kinds: change the schemata
definition for kind 777 to mark it as conditional (set hasConditionals=true or
equivalent flag in the source schema) or adjust the category mapping logic so
that a conditional kind yields category "conditional" only when hasConditionals
is true; then re-run `@nostrability/schemata-codegen` to regenerate the file (look
for the schema entry for kind 777 or the mapping function that derives
category/hasConditionals) and run tests to ensure consistency with other
"nipless" conditional kinds.
- Escape e.path in dashboard error rendering (XSS prevention) - Validate numeric query params in /api/results (return 400 on NaN) - Handle SSE write errors by removing stale clients - Guard initDB against double initialization - Defensive try/catch on JSON.parse in rowToResult - Add language specifiers to README fenced code blocks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/dashboard.ts (1)
400-402: Consider adding single-quote escaping for defense in depth.The
escapeHtmlfunction handles&,<,>, and", but omits single quotes ('). While current usage only involves double-quoted attributes, adding.replace(/'/g, ''')provides additional protection if the function is used in other contexts.♻️ More complete escaping
function escapeHtml(s) { - return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); + return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/dashboard.ts` around lines 400 - 402, The escapeHtml function currently replaces &, <, > and " but omits single quotes; update the escapeHtml implementation (function escapeHtml) to also replace single quotes by adding a .replace(/'/g,''') to the chain so single-quote characters are HTML-escaped (defense-in-depth for usages in single-quoted attributes or other contexts).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@README.md`:
- Around line 36-41: README counts are out of sync with the registry: validate
whether the doc should reference KNOWN_KIND_NUMBERS (179 entries) or the full
KIND_REGISTRY (180 entries), then update the table counts to match that chosen
source; specifically adjust the "base-only" count from 46 to 47 and the total
from 179 to 180 if you choose KIND_REGISTRY (or change the total to 179 and keep
base-only 47 if you choose KNOWN_KIND_NUMBERS), and ensure the README text and
the table rows referencing "validated", "base-only", and "total" reflect the
final numbers; check symbols KIND_REGISTRY and KNOWN_KIND_NUMBERS to determine
which to cite in the doc.
---
Nitpick comments:
In `@src/dashboard.ts`:
- Around line 400-402: The escapeHtml function currently replaces &, <, > and "
but omits single quotes; update the escapeHtml implementation (function
escapeHtml) to also replace single quotes by adding a .replace(/'/g,''') to
the chain so single-quote characters are HTML-escaped (defense-in-depth for
usages in single-quoted attributes or other contexts).
🪄 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: 57614c21-fb7d-4c77-9a70-6198057620c6
📒 Files selected for processing (4)
README.mdsrc/dashboard.tssrc/index.tssrc/store.ts
| | Tier | Meaning | Count | | ||
| |------|---------|-------| | ||
| | **validated** | Schema exists with tag/content constraints — fully checked | 133 kinds | | ||
| | **base-only** | Known kind but no tag/content constraints in schemata — only NIP-01 base fields checked | 46 kinds | | ||
| | **unknown** | Kind not in schemata at all — only NIP-01 base fields checked | everything else | | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify kind counts in the codebase match README documentation
echo "=== Counting kinds in kind-registry.ts ==="
# Count KNOWN_KIND_NUMBERS array length
echo "Total known kinds:"
rg -oP '\d+' src/kind-registry.ts | head -1000 | wc -l
# More precise: count entries in KNOWN_KIND_NUMBERS array
ast-grep --pattern 'export const KNOWN_KIND_NUMBERS: readonly number[] = [$$$]' | head -50
# Count entries where category !== "bare" (validated kinds)
echo ""
echo "Checking KIND_REGISTRY entries..."
rg 'category:' src/kind-registry.ts | head -20
# Count bare vs non-bare
echo ""
echo "Bare category count:"
rg -c 'category: "bare"' src/kind-registry.ts || echo "0"
echo ""
echo "Non-bare (validated) kinds:"
rg 'category:' src/kind-registry.ts | rg -v '"bare"' | wc -lRepository: nostrability/test-relay
Length of output: 2560
🏁 Script executed:
#!/bin/bash
# Get precise count of KNOWN_KIND_NUMBERS array entries
echo "=== Counting KNOWN_KIND_NUMBERS array entries ==="
# Extract just the array content and count numbers
sed -n '/export const KNOWN_KIND_NUMBERS/,/^\]/p' src/kind-registry.ts | grep -oP '\d+' | wc -l
echo ""
echo "=== Verify total kinds in KIND_REGISTRY ==="
# Count total KIND_REGISTRY entries
rg -c 'kind:' src/kind-registry.ts | head -1
echo ""
echo "=== Double-check bare count ==="
rg -c 'category: "bare"' src/kind-registry.ts
echo ""
echo "=== Count all category types ==="
rg -o 'category: "[^"]*"' src/kind-registry.ts | sort | uniq -cRepository: nostrability/test-relay
Length of output: 367
Update kind counts in documentation to match current registry data.
The documented counts in the README (133 validated, 46 base-only, 179 total) are out of sync with the actual kind-registry.ts:
- Validated kinds: 133 ✓ (correct)
- Base-only kinds: 47 (documented as 46)
- Total kinds in
KIND_REGISTRY: 180 (documented as 179)
Confirm whether the documentation should reference KNOWN_KIND_NUMBERS (179 entries) or the full KIND_REGISTRY (180 entries), then update the counts and totals accordingly.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@README.md` around lines 36 - 41, README counts are out of sync with the
registry: validate whether the doc should reference KNOWN_KIND_NUMBERS (179
entries) or the full KIND_REGISTRY (180 entries), then update the table counts
to match that chosen source; specifically adjust the "base-only" count from 46
to 47 and the total from 179 to 180 if you choose KIND_REGISTRY (or change the
total to 179 and keep base-only 47 if you choose KNOWN_KIND_NUMBERS), and ensure
the README text and the table rows referencing "validated", "base-only", and
"total" reflect the final numbers; check symbols KIND_REGISTRY and
KNOWN_KIND_NUMBERS to determine which to cite in the doc.
Summary
/api/results,/api/summary,/api/pubkeys)Validation pipeline
Crypto failures are rejected (
OK false). Schema errors are accepted (OK true) with error details — clients keep working while devs see what needs fixing.Commits
Each commit is standalone and builds on the previous:
Scaffold NIP-01 WebSocket relay skeleton— minimal relay, passes nak publishAdd schemata validation and cryptographic verification— vendors codegen output, adds id/sig verificationAdd SQLite validation store and REST API— better-sqlite3 persistence, three query endpointsAdd real-time web dashboard with SSE live updates— self-contained HTML, live event streamAdd README— docs, quick start, architecture, nostrability ecosystem contextTest plan
npm install && npm run build && npm start— relay starts on ws://localhost:7777OK false)curl /api/summaryreturns correct pass/fail countscurl /api/pubkeyslists seen pubkeys with counts🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation