355 lines
11 KiB
Markdown
355 lines
11 KiB
Markdown
# NIP-DBSYNC — Minimal Mutation Events over Nostr
|
|
|
|
`draft` `optional`
|
|
|
|
Defines a minimal event format for publishing immutable application mutation events over Nostr.
|
|
|
|
This draft intentionally standardizes only the wire format for mutation transport. It does **not** standardize database replication strategy, conflict resolution, relay retention, or key derivation.
|
|
|
|
---
|
|
|
|
## Abstract
|
|
|
|
This NIP defines one regular event kind, **5000**, for signed mutation events.
|
|
|
|
A mutation event identifies:
|
|
|
|
- the object namespace being mutated,
|
|
- the object identifier within that namespace,
|
|
- the mutation operation,
|
|
- an optional parent mutation event,
|
|
- an application-defined payload.
|
|
|
|
The purpose of this NIP is to make signed mutation logs portable across Nostr clients and relays without requiring relays to implement database-specific behavior.
|
|
|
|
---
|
|
|
|
## Motivation
|
|
|
|
Many applications need a way to distribute signed state changes across multiple publishers, consumers, or services.
|
|
|
|
Today this can be done with private event kinds, but private schemas make cross-implementation interoperability harder than necessary. This NIP defines a small shared envelope for mutation events while leaving application-specific state semantics in the payload.
|
|
|
|
This NIP is intended for use cases such as:
|
|
|
|
- synchronizing object changes between cooperating services,
|
|
- publishing auditable mutation logs,
|
|
- replaying application events from ordinary Nostr relays,
|
|
- bridging non-Nostr systems into a Nostr-based event stream.
|
|
|
|
This NIP is **not** a consensus protocol. It does not provide:
|
|
|
|
- total ordering,
|
|
- transactional guarantees,
|
|
- global conflict resolution,
|
|
- authorization rules,
|
|
- guaranteed relay retention.
|
|
|
|
Applications that require those properties MUST define them separately.
|
|
|
|
---
|
|
|
|
## Specification
|
|
|
|
### Event Kind
|
|
|
|
| Kind | Category | Name |
|
|
|------|----------|------|
|
|
| 5000 | Regular | Mutation |
|
|
|
|
Kind `5000` is a regular event. Relays that support this NIP MAY store it like any other regular event.
|
|
|
|
This NIP does **not** require relays to:
|
|
|
|
- retain all historical events,
|
|
- index any specific tag beyond normal NIP-01 behavior,
|
|
- deliver events in causal or chronological order,
|
|
- detect or resolve conflicts.
|
|
|
|
Applications that depend on durable replay or custom indexing MUST choose relays whose policies satisfy those needs.
|
|
|
|
### Event Structure
|
|
|
|
```json
|
|
{
|
|
"id": "<32-byte lowercase hex>",
|
|
"pubkey": "<32-byte lowercase hex>",
|
|
"created_at": "<unix timestamp, seconds>",
|
|
"kind": 5000,
|
|
"tags": [
|
|
["r", "<resource namespace>"],
|
|
["i", "<object identifier>"],
|
|
["op", "<mutation operation>"],
|
|
["e", "<parent mutation event id>"]
|
|
],
|
|
"content": "<JSON-encoded application payload>",
|
|
"sig": "<64-byte lowercase hex>"
|
|
}
|
|
```
|
|
|
|
The `content` field is a JSON-encoded string. Its structure is defined below.
|
|
|
|
---
|
|
|
|
## Tags
|
|
|
|
| Tag | Required | Description |
|
|
|-----|----------|-------------|
|
|
| `r` | Yes | Stable resource namespace for the mutated object type. Reverse-DNS style names are RECOMMENDED, for example `com.example.accounts.user`. |
|
|
| `i` | Yes | Opaque object identifier, unique within the `r` namespace. Consumers MUST treat this as a string. |
|
|
| `op` | Yes | Mutation operation. This NIP defines only `upsert` and `delete`. |
|
|
| `e` | No | Parent mutation event id, if the publisher wants to express ancestry. At most one `e` tag SHOULD be included in this version of the protocol. |
|
|
| `v` | No | Application payload schema version as a string. RECOMMENDED when the payload format may evolve over time. |
|
|
|
|
### Tag Rules
|
|
|
|
Publishers:
|
|
|
|
- MUST include exactly one `r` tag.
|
|
- MUST include exactly one `i` tag.
|
|
- MUST include exactly one `op` tag.
|
|
- MUST set `op` to either `upsert` or `delete`.
|
|
- SHOULD include at most one `e` tag.
|
|
- MAY include one `v` tag.
|
|
|
|
Consumers:
|
|
|
|
- MUST ignore unknown tags.
|
|
- MUST NOT assume tag ordering.
|
|
- MUST treat the `e` tag as an ancestry hint, not as proof of global ordering.
|
|
|
|
### Resource Namespaces
|
|
|
|
The `r` tag identifies an application-level object type.
|
|
|
|
This NIP does not define a global registry of resource namespaces. To reduce collisions, publishers SHOULD use a stable namespace they control, such as reverse-DNS notation.
|
|
|
|
Examples:
|
|
|
|
- `com.example.accounts.user`
|
|
- `org.example.inventory.item`
|
|
- `net.example.billing.invoice`
|
|
|
|
Publishers MUST document the payload schema associated with each resource namespace they use.
|
|
|
|
---
|
|
|
|
## Content Payload
|
|
|
|
The `content` field MUST be a JSON-encoded object.
|
|
|
|
```json
|
|
{
|
|
"value": {},
|
|
"patch": "merge"
|
|
}
|
|
```
|
|
|
|
| Field | Required | Description |
|
|
|-------|----------|-------------|
|
|
| `value` | Yes | Application-defined mutation payload. For `upsert`, this is the state fragment or full post-mutation state being published. For `delete`, this MAY be an empty object or a small reason object. |
|
|
| `patch` | No | How `value` should be interpreted. This NIP defines `merge` and `replace`. If omitted, consumers MUST treat it as application-defined. |
|
|
|
|
### Payload Rules
|
|
|
|
For `op = upsert`:
|
|
|
|
- `value` MUST be a JSON object.
|
|
- Publishers SHOULD publish either:
|
|
- a partial object intended to be merged, or
|
|
- a full post-mutation object intended to replace prior state.
|
|
- If the interpretation is important for interoperability, publishers SHOULD set `patch` to `merge` or `replace`.
|
|
|
|
For `op = delete`:
|
|
|
|
- `value` MAY be `{}`.
|
|
- Consumers MUST treat `delete` as an application-level tombstone signal.
|
|
- This NIP does not define whether deletion means hard delete, soft delete, archival, or hiding. Applications MUST define that separately.
|
|
|
|
### Serialization
|
|
|
|
All payload values MUST be JSON-serializable.
|
|
|
|
The following representations are RECOMMENDED:
|
|
|
|
| Type | Representation |
|
|
|------|----------------|
|
|
| Timestamp / datetime | ISO 8601 string |
|
|
| Decimal | String |
|
|
| Binary | Base64 string |
|
|
| Null | JSON `null` |
|
|
|
|
Publishers MAY define additional type mappings, but those mappings are application-specific and MUST be documented outside this NIP.
|
|
|
|
---
|
|
|
|
## Ancestry and Replay
|
|
|
|
The optional `e` tag allows a publisher to indicate which prior mutation event it considered the parent when creating a new mutation.
|
|
|
|
This supports applications that want ancestry hints for:
|
|
|
|
- local conflict detection,
|
|
- replay ordering,
|
|
- branch inspection,
|
|
- audit tooling.
|
|
|
|
However:
|
|
|
|
- the `e` tag does **not** create a global ordering guarantee,
|
|
- relays are not required to deliver parents before children,
|
|
- consumers MUST be prepared to receive out-of-order events,
|
|
- consumers MAY buffer, defer, ignore, or immediately apply parent-missing events according to local policy.
|
|
|
|
This NIP does not define a merge event format.
|
|
|
|
This NIP does not define conflict resolution. If two valid mutation events for the same `(r, i)` object are concurrent or incompatible, consumers MUST resolve them using application-specific rules.
|
|
|
|
---
|
|
|
|
## Authorization
|
|
|
|
This NIP does not define who is authorized to publish mutation events for a given resource or object.
|
|
|
|
Authorization is application-specific.
|
|
|
|
Consumers MUST NOT assume that a valid Nostr signature alone authorizes a mutation. Consumers MUST apply their own trust policy, which MAY include:
|
|
|
|
- explicit pubkey allowlists,
|
|
- per-resource ACLs,
|
|
- external capability documents,
|
|
- relay-level write restrictions,
|
|
- application-specific verification.
|
|
|
|
This NIP does not define custodial keys, deterministic key derivation, shared cluster secrets, or delegation schemes.
|
|
|
|
---
|
|
|
|
## Relay Behavior
|
|
|
|
A relay implementing only NIP-01 remains compatible with this NIP.
|
|
|
|
No new relay messages are required beyond `REQ`, `EVENT`, and `CLOSE`.
|
|
|
|
Relays:
|
|
|
|
- MAY index the `r` and `i` tags using existing single-letter tag indexing conventions.
|
|
- MAY apply normal retention, rate-limit, and access-control policies.
|
|
- MAY reject events that are too large or otherwise violate local policy.
|
|
- MUST NOT be expected to validate application payload semantics.
|
|
|
|
Applications that require stronger guarantees, such as durable retention or strict admission control, MUST obtain those guarantees from relay policy or from a separate protocol profile.
|
|
|
|
---
|
|
|
|
## Subscription Filters
|
|
|
|
This NIP works with ordinary NIP-01 filters.
|
|
|
|
### All mutations for one resource
|
|
|
|
```json
|
|
{
|
|
"kinds": [5000],
|
|
"#r": ["com.example.accounts.user"]
|
|
}
|
|
```
|
|
|
|
### Mutation history for one object
|
|
|
|
```json
|
|
{
|
|
"kinds": [5000],
|
|
"#r": ["com.example.accounts.user"],
|
|
"#i": ["550e8400-e29b-41d4-a716-446655440000"]
|
|
}
|
|
```
|
|
|
|
### Mutations from trusted authors
|
|
|
|
```json
|
|
{
|
|
"kinds": [5000],
|
|
"authors": [
|
|
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
|
|
]
|
|
}
|
|
```
|
|
|
|
Applications SHOULD prefer narrow subscriptions over broad network-wide firehoses.
|
|
|
|
---
|
|
|
|
## Examples
|
|
|
|
### Upsert with parent
|
|
|
|
```json
|
|
{
|
|
"id": "1111111111111111111111111111111111111111111111111111111111111111",
|
|
"pubkey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
|
"created_at": 1710500300,
|
|
"kind": 5000,
|
|
"tags": [
|
|
["r", "com.example.accounts.user"],
|
|
["i", "550e8400-e29b-41d4-a716-446655440000"],
|
|
["op", "upsert"],
|
|
["e", "0000000000000000000000000000000000000000000000000000000000000000"],
|
|
["v", "1"]
|
|
],
|
|
"content": "{\"value\":{\"email\":\"jane.doe@newdomain.com\",\"updated_at\":\"2025-03-15T14:35:00Z\"},\"patch\":\"merge\"}",
|
|
"sig": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
|
}
|
|
```
|
|
|
|
### Delete tombstone
|
|
|
|
```json
|
|
{
|
|
"id": "2222222222222222222222222222222222222222222222222222222222222222",
|
|
"pubkey": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
"created_at": 1710500600,
|
|
"kind": 5000,
|
|
"tags": [
|
|
["r", "com.example.accounts.user"],
|
|
["i", "550e8400-e29b-41d4-a716-446655440000"],
|
|
["op", "delete"],
|
|
["e", "1111111111111111111111111111111111111111111111111111111111111111"],
|
|
["v", "1"]
|
|
],
|
|
"content": "{\"value\":{\"reason\":\"user_requested\"}}",
|
|
"sig": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
- **Unauthorized writes:** A valid signature proves authorship, not authorization. Consumers MUST enforce their own trust policy.
|
|
- **Replay:** Old valid events may be redelivered by relays or attackers. Consumers SHOULD deduplicate by event id and apply local replay policy.
|
|
- **Reordering:** Events may arrive out of order. Consumers MUST NOT treat `created_at` or `e` as a guaranteed total order.
|
|
- **Conflict flooding:** Multiple valid mutations may target the same object. Consumers SHOULD rate-limit, bound buffering, and define local conflict policy.
|
|
- **Sensitive data exposure:** Nostr events are typically widely replicable. Publishers SHOULD NOT put secrets or regulated data in mutation payloads unless they provide application-layer encryption.
|
|
- **Relay retention variance:** Some relays will prune history. Applications that depend on full replay MUST choose relays accordingly or maintain an external archive.
|
|
|
|
---
|
|
|
|
## Extension Points
|
|
|
|
Future drafts or companion NIPs may define:
|
|
|
|
- snapshot events for faster bootstrap,
|
|
- object-head or checkpoint events,
|
|
- capability or delegation profiles for authorized writers,
|
|
- standardized conflict-resolution profiles for specific application classes.
|
|
|
|
Such extensions SHOULD remain optional and MUST NOT change the meaning of kind `5000` mutation events defined here.
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) — Basic protocol flow description
|