NRP Remote Exchange Spec
Purpose
This document defines the wire contract for remote NRP resolution over HTTP.
Goal:
- keep the exchange compatible with the current
cleakerpointer runtime - avoid coupling the kernel to HTTP route quirks
- leave room for future transport formats without changing
.me
Decision
Use plain JSON as the canonical exchange format for now.
Do not require JSON-LD yet.
Reason:
- current client code already consumes plain JSON
- the system still needs routing and normalization to stabilize
- JSON-LD can be added later as an alternate representation, not as the first contract
Exchange Layers
Semantic request
The client conceptually asks for:
- a canonical NRP target
- an operation kind
- optional authorization context
Example semantic target:
nrp://cleaker.me/users/ana/read/profile.name
Transport request
The HTTP transport maps that target into:
- method
- host
- path
- headers
- query
Example:
GET /profile/nameHost: ana.cleaker.me
Canonical Operations
read
Read a resolved semantic value from a remote namespace.
write
Append a thought or memory event into a remote namespace.
claim
Forge or reserve namespace identity.
open
Verify the namespace trinity and recover replay state.
Response Envelope
All remote HTTP responses should use a common envelope.
{
"ok": true,
"operation": "read",
"transport": {
"protocol": "https",
"method": "GET"
},
"target": {
"nrp": "nrp://cleaker.me/users/ana/read/profile.name",
"namespace": "cleaker.me/users/ana",
"path": "profile.name"
},
"result": {
"value": "Ana"
},
"meta": {
"resolvedAt": 1773200000000
}
}Rules:
okis always presentoperationis always presenttarget.namespaceis always canonicaltarget.nrpis the semantic identity of the resolved request- operation-specific payload goes inside
result - operational metadata goes inside
meta
Read Contract
Request
Example:
GET /profile/name HTTP/1.1
Host: ana.cleaker.me
Accept: application/jsonSuccessful response
{
"ok": true,
"operation": "read",
"target": {
"nrp": "nrp://cleaker.me/users/ana/read/profile.name",
"namespace": "cleaker.me/users/ana",
"path": "profile.name"
},
"result": {
"value": "Ana"
},
"meta": {
"resolvedAt": 1773200000000,
"namespaceRecordVerified": false
}
}Not found
{
"ok": false,
"operation": "read",
"target": {
"nrp": "nrp://cleaker.me/users/ana/read/profile.unknown",
"namespace": "cleaker.me/users/ana",
"path": "profile.unknown"
},
"error": {
"code": "PATH_NOT_FOUND",
"message": "No value was resolved for the requested path."
},
"meta": {
"resolvedAt": 1773200000000
}
}Write Contract
Request
POST / HTTP/1.1
Host: ana.cleaker.me
Content-Type: application/json{
"identityHash": "claim-hash",
"expression": "profile.name",
"value": "Ana",
"payload": {
"path": "profile.name",
"value": "Ana"
}
}Successful response
{
"ok": true,
"operation": "write",
"target": {
"nrp": "nrp://cleaker.me/users/ana/write/profile.name",
"namespace": "cleaker.me/users/ana",
"path": "profile.name"
},
"result": {
"blockId": "uuid",
"timestamp": 1773200000000
}
}Claim Contract
Request
{
"namespace": "ana.cleaker",
"secret": "luna"
}Successful response
{
"ok": true,
"operation": "claim",
"target": {
"nrp": "nrp://cleaker.me/users/ana/claim",
"namespace": "ana.cleaker"
},
"result": {
"identityHash": "derived-hash"
},
"meta": {
"createdAt": 1773200000000
}
}Open Contract
Successful response
{
"ok": true,
"operation": "open",
"target": {
"nrp": "nrp://cleaker.me/users/ana/open",
"namespace": "ana.cleaker"
},
"result": {
"identityHash": "derived-hash",
"noise": "seeded-noise",
"memories": [],
"openedAt": 1773200000000
},
"meta": {
"namespaceRecordVerified": true
}
}Error Contract
All failures should use this shape:
{
"ok": false,
"operation": "read",
"target": {
"nrp": "nrp://cleaker.me/users/ana/read/profile.name",
"namespace": "cleaker.me/users/ana",
"path": "profile.name"
},
"error": {
"code": "PATH_NOT_FOUND",
"message": "No value was resolved for the requested path."
},
"meta": {
"resolvedAt": 1773200000000
}
}Rules:
error.codeis stable and machine-readableerror.messageis human-readable- HTTP status and JSON error code must agree
Recommended mappings:
400->BAD_REQUEST401->UNAUTHORIZED403->NAMESPACE_WRITE_FORBIDDEN404->PATH_NOT_FOUNDorCLAIM_NOT_FOUND409->NAMESPACE_TAKEN422->CLAIM_VERIFICATION_FAILED500->INTERNAL_ERROR
Compatibility Rule
During migration, the server may continue returning the current minimal read shape:
{
"ok": true,
"namespace": "cleaker.me/users/ana",
"path": "profile.name",
"value": "Ana"
}Client normalization should accept both:
- legacy minimal shape
- full response envelope
Client Normalization
The remotePointer client should normalize read responses as follows:
- If
result.valueexists, use that. - Else if top-level
valueexists, use that. - Else return the full payload as raw data.
This preserves compatibility with the current server while allowing a richer contract.
Future Extension
After the envelope stabilizes, add optional:
@context- signed namespace records
- response signatures
- content negotiation for
application/ld+json
But those should be additive, not required for the first remote contract.