Skip to content

Firehose phase 1: validated profiles hose (kind 0) + extensible hose seam - #8

Merged
melvincarvalho merged 2 commits into
gh-pagesfrom
issue-7-profiles-hose
Jun 17, 2026
Merged

Firehose phase 1: validated profiles hose (kind 0) + extensible hose seam#8
melvincarvalho merged 2 commits into
gh-pagesfrom
issue-7-profiles-hose

Conversation

@melvincarvalho

Copy link
Copy Markdown
Contributor

Closes #7. First step of the phased firehose rework — profiles only, but it lays down the seam later phases reuse.

What

  • Hose seam (src/hoses/): a hose is a small module { name, kinds, ensureIndexes(db), ingest(event, db) }. src/indexer.js is now a generic runner — it subscribes to the union of registered hoses' kinds and dispatches each event to the matching hose. Adding follows / relay-lists / relay-health later = drop in a sibling module.
  • src/hoses/profiles.js — the first concrete hose (kind 0):
    • Schnorr signature verification before storing: recompute the NIP-01 event id (sha256 of the canonical serialization) and verify the sig with @noble/curves. Rejects wrong kind, malformed fields, non-JSON content, tampered id, and forged sigs. A malicious relay can no longer inject a forged did:nostr profile.
    • Owns its indexes: pubkey, created_at, and the content_text text index — so /api/search works on a fresh deploy (today that index only exists because it was created by hand).
    • Latest-wins upsert into beacon (unchanged doc shape).
  • indexer.js: kind 0 → profiles hose; kinds 3 and 10002 keep the existing upsertEvent path (migrated in their own phases — no behaviour change).
  • deps: @noble/curves, @noble/hashes (keeps the repo's "no nostr-tools" approach).

Out of scope (next phases)

Follows hose (2) · relay-lists hose (3) · active relay-health prober that unfreezes /relays (4) · deploy + retire the legacy server hoses (5).

Verification

  • npm test17/17 pass (8 existing + 9 new profiles-hose tests: valid, empty content, tampered content/id, forged sig, non-hex pubkey, wrong kind, non-JSON content, malformed input).
  • Integration smoke against Mongo: ensureIndexes creates pubkey_1 / created_at_-1 / content_text; latest-wins keeps the newest event; stale + forged events rejected.

Note on deployment

The live beacon-new on nostr.social runs an old copied (non-git) serve.js from before the /relays page, and the indexing there is still the legacy firehose/followshose processes — so this hose isn't exercised in prod yet. Wiring the server to a real git checkout + running our indexer is the phase-5 consolidation.

Introduce a composable 'hose' seam for the firehose indexer and ship the
first concrete hose: profiles (kind 0).

- src/hoses/profiles.js: declares its kinds, owns its Mongo indexes
  (pubkey, created_at, and the content_text search index so /api/search
  works on a fresh deploy), and verifies each event's schnorr signature
  (NIP-01 id recompute + @noble/curves) before a latest-wins upsert.
  A relay can no longer inject a forged did:nostr profile.
- src/indexer.js: generic hose registry — subscribe to the union of
  registered hoses' kinds and dispatch by kind. Kind 0 routes through the
  profiles hose; kinds 3 and 10002 keep the legacy upsert path until
  their own phases (no behaviour change).
- deps: @noble/curves, @noble/hashes (keeps the no-nostr-tools approach).
- test/profiles-hose.test.js: sign real kind-0 events and assert
  accept/reject (valid, empty content, tampered content/id, forged sig,
  non-hex pubkey, wrong kind, non-JSON content, malformed input).

Closes #7
Make the firehose a switch so a deploy can run a single hose without
double-writing against the legacy firehose/followshose processes during
the phased migration.

- planIngest(allHoses, env): pure, testable selector. HOSES env (comma
  list of hose names; default: all registered) picks active hoses;
  INDEX_LEGACY_KINDS (default on) gates the raw-upsert fallback for kinds
  not yet migrated to a hose (3, 10002). Subscription kinds = union of
  enabled hoses' kinds + legacy kinds, with a hose-owned kind removed from
  the legacy set so nothing is double-subscribed.
- runIndexer: use the plan; warn on unknown hose names / empty kind set.
- .env.example: document HOSES + INDEX_LEGACY_KINDS.
- test/indexer-plan.test.js: 8 cases (defaults, legacy off, unknown names,
  single-hose deploy, hose-owned legacy kind removal).

Default behaviour unchanged: profiles hose + legacy 3/10002.
@melvincarvalho
melvincarvalho merged commit 6751e6d into gh-pages Jun 17, 2026
@melvincarvalho
melvincarvalho deleted the issue-7-profiles-hose branch June 17, 2026 08:44
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.

Firehose phase 1: validated profiles hose (kind 0) + extensible hose seam

1 participant