# Neo4j schema

Legenda typów: `kind`, `created_at`, `position_in_event`, `position`, `last_checked_at` → **Long**; `active` → **Boolean**; reszta → **String**.

Jednostki czasu: `Event.created_at` = **sekundy** (unix), `Relay.last_checked_at` = **milisekundy** (Neo4j `timestamp()`). Nie mieszać w `WHERE`.

Nodes (`*` = required):

- `Event`: `id*`, `kind`, `created_at`, `content`, `sig`, `raw` — **brak pola `pubkey`**
- `Pubkey`: `hex*` (64 hex; npub konwertuj przez MCP), `relay_list_ts` (created_at ostatniego sparsowanego kind-10002; pilnuje replaceable-event dla NIP-65)
- `Profile`: `pubkey*` (= Pubkey.hex; jeden Profile per Pubkey), `created_at` (z kind-0), `raw` (surowy `content` kind-0), oraz pola z NIP-01: `name`, `display_name`, `about`, `picture`, `banner`, `nip05`, `lud16`, `lud06`, `website`. Zawartość nadpisywana tylko gdy nowy kind-0 ma większe `created_at` (replaceable event); `null` w źródłowym JSON = usunięcie property z węzła.
- `Relay`: `url*`, `status*`, `last_checked_at*`, `last_error`, `active`
- `Tag`: `tag_id*`, `name*`, `position_in_event*` — **NIE** `index_in_event`
- `TagElement`: `element_id*`, `position*`, `text*` — **NIE** `value`; `position=0` to powtórzona nazwa tagu (`= Tag.name`), wartości są od `position=1`
- `Addressable`: `value*`, `kind*`, `pubkey*`, `d*`

Relationships (bez properties):

- `(Event)-[:AUTHORED_BY]->(Pubkey)`
- `(Event)-[:SEEN_ON]->(Relay)`
- `(Event)-[:HAS_TAG]->(Tag)`
- `(Tag)-[:HAS_ELEMENT]->(TagElement)`
- `(Tag)-[:REFERS_TO]->(Event|Pubkey|Relay|Addressable)` — dla tagów `e`/`p`/`r`/`a`; `r` tylko w kind 10002 (NIP-65)
- `(Pubkey)-[:HAS_PROFILE]->(Profile)` — 1:1; Profile pojawia się dopiero po ingescie kind-0 dla danego Pubkey. `users.json` pozostaje tylko fallbackiem dla pubkeyów bez kind-0 w grafie.
- `(Pubkey)-[:PUBLISHES_TO]->(Relay)` — autor publikuje tam swoje eventy (NIP-65 "write" lub brak markera). Budowane z kind-10002; pełny rebuild per zdarzenie (stara lista = usuwana). Używane przez `update_graph_with_events_authored_by_outbox` do targetowanego REQ.
- `(Pubkey)-[:READS_FROM]->(Relay)` — autor nasłuchuje tam (NIP-65 "read" lub brak markera). Do użycia gdy chcesz dostarczyć event **do** konkretnej osoby (mentiony, DM-y).

Name resolution (zamiast `users.json`):

```cypher
MATCH (p:Pubkey)-[:HAS_PROFILE]->(pr:Profile)
RETURN p.hex, coalesce(pr.display_name, pr.name, pr.nip05) AS label
```

Po ingescie nowych pubkeyów (np. nowi follows) kind-0 dla nich trzeba dociągnąć — `update_graph_with_events_authored_by(hex)` z `limit=1` albo `update_graph_with_events_matching_filter({"authors":[...], "kinds":[0], "limit":1})`. Dla istniejących w grafie kind-0 bez Profile: MCP tool `backfill_profiles` (jednorazowo, idempotentnie).
