The KOI-net protocol defines the standard communication patterns and coordination norms needed to establish and maintain Knowledge Organization Infrastructure (KOI) networks. KOI networks are heterogeneous compositions of KOI nodes, each of which is capable of autonomously inputting, processing, and outputting knowledge. The behavior of each node and configuration of each network can vary greatly; therefore, the protocol is designed to be a simple, flexible, and interoperable foundation for future projects to build on. The protocol only governs communication between nodes, not how they operate internally. As a result we consider KOI-nets to be fractal-like, in that a network of nodes may act like a single node when viewed from outside.
At the foundation of the KOI-net protocol are four fundamental protocol objects: RIDs, manifests, bundles, and events. These objects are used in the protocol to communicate knowledge between nodes.
graph TB
subgraph Event
ET["event_type:<br>NEW | UPDATE | FORGET"]
subgraph Bundle
subgraph Manifest
RID["rid: URI string"]
TS[timestamp: ISO 8601]
Hash[sha256_hash:<br>hex digest string]
end
Contents["contents: JSON object"]
end
end
Figure 1: Relationship between protocol objects and internal fields.
Reference Identifiers (or RIDs), are the primary identifiers used throughout the KOI-net protocol to identify references to knowledge resources. Manifests, bundles, and events all use RIDs to indicate the resource they are associated with or referring to. RIDs take the form of URI strings. All RIDs used within the KOI-net protocol MUST comply with the RID protocol.
Manifests are portable descriptors of RIDed data objects. Each manifest is composed of an RID, timestamp, and SHA-256 hash. While manifests are inherently linked to the data objects they describe, they may also be independently passed around within a network. This allows nodes to share metadata about data they possess, such as the version (i.e., timestamp, hash) of the data they have cached.
Manifests SHOULD be generated for data that a node possesses, or processed when received from other nodes. Generally nodes will store manifests alongside the data they describe as a bundle. See [[#2.3. Bundles]] for more information.
Manifest objects MUST conform to the following JSON schema and requirements:
{
"title": "Manifest",
"type": "object",
"properties": {
"rid": {
"type": "string"
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"sha256_hash": {
"type": "string"
}
},
"required": [
"rid",
"timestamp",
"sha256_hash"
]
}
timestamp MUST be an ISO 8601 compliant date and time string in extended format. It SHOULD be in UTC and MUST NOT be in local time without a UTC offset.sha256_hash MUST be the hex digest of the SHA-256 hash of the data object that the manifest describes. If the data object is JSON data, it SHOULD be canonicalized according to RFC 8785 before hashing.Bundles are instances of an RIDed data object. Each bundle is composed of a manifest and contents (the data). The metadata and data are “bundled” together into a single object which represents a snapshot of its state, at the manifest’s timestamp.
Bundle objects MUST conform to the following JSON schema and requirements:
{
"title": "Bundle",
"type": "object",
"properties": {
"manifest": {
"$ref": "./manifest.schema.json"
},
"contents": {
"type": "object"
}
},
"required": [
"manifest",
"contents"
]
}
contents MUST be a valid JSON object. A future version of this protocol may support other types of data, including binary formats.contents MUST be validated for RID types koi-net.node and koi-net.edge (see sections [[#3.1.2. Node Profiles]] and [[#3.2.2. Edge Profiles]]).Events are signaling constructs that convey information about RIDed resources between networked nodes. Events can be thought of wrapping RIDs, manifests, or bundles with an event type. Each event is composed of an event type, an RID, and an optional manifest and contents. An event is considered to contain a bundle when it has both a manifest and contents.
There are three event types: NEW, UPDATE, and FORGET (a.k.a. “FUN” events). In general, events signal a change in a node’s internal state:
NEW: Indicates a previously unknown RID was saved.UPDATE: Indicates a previously known RID was saved.FORGET: Indicates a previously known RID was deleted.In certain cases, however, events may reflect a node’s intention rather than its internal state. For example, in a node handshake the initiator will broadcast a NEW event for its own RID, despite “previously knowing” itself. See [[#4.2.1. Node Handshake]] for more information.
As opposed to CRUD (create, read, update, delete), events are messages, not operations. Each node has autonomy in deciding how to react based on the message it receives. The simplest pattern is to simply mirror another node, i.e., to save data to cache on a NEW or UPDATE event, and delete it on a FORGET event.
Some events may include only an RID or manifest. In this case, nodes should take a “lazy dereference” approach, first validating the provided RID or manifest and deciding whether the knowledge is desired, and then requesting the bundle from the source node for full processing.
Event objects MUST conform to the following JSON schema and requirements:
{
"$defs": {
"EventType": {
"type": "string",
"enum": [
"NEW",
"UPDATE",
"FORGET"
]
}
},
"title": "Event",
"type": "object",
"properties": {
"rid": {
"type": "string"
},
"event_type": {
"$ref": "#/$defs/EventType"
},
"manifest": {
"$ref": "./manifest.schema.json"
},
"contents": {
"type": "object"
}
},
"required": [
"rid",
"event_type"
]
}
rid MUST be the same as the rid in the manifest object, if present.FORGET event SHOULD only include an RID, not a manifest or bundle.NEW or UPDATE event SHOULD include a bundle, unless the data object is large, in which case the event SHOULD only include a manifest.The KOI-net protocol is used to build networks of knowledge processing nodes. Network communication is composed of both pull-based and push-based operations, allowing knowledge to freely flow into and out of nodes.
Nodes are the fundamental building blocks of a network. Each node is a self contained knowledge processing unit, capable of autonomously requesting, receiving, processing, storing, broadcasting, and serving knowledge objects.
Nodes are identified using RIDs. Node RIDs are Object Reference Names (ORNs) in the “KOI-net node” namespace, with the RID type: orn:koi-net.node. The reference component is composed of a human-readable name and the SHA-256 hash of the node’s public key. If the public key changes for any reason, a node MUST adopt a new RID, and SHOULD be considered a new entity in the network.
Node RIDs MUST be constructed according to the following pattern and requirements:
orn:koi-net.node:<name>+<hash>
name SHOULD be a brief human-readable descriptor of the node’s function (e.g. “coordinator”, “slack-sensor”).hash MUST be the hex digest of the SHA-256 hash of the node’s public key, as encoded in the node profile.A node profile describes important metadata about a node in a network: a node’s type (full or partial), the base URL of its API (for full nodes), the RID types for which it provides state and events, and its public key. This is the data object that a node RID refers to; thus, the contents of a node RID bundle MUST be a valid node profile.
This protocol defines two types of nodes:
| Node type | State can be fetched | Receives events via webhook | Receives events via polling |
|---|---|---|---|
| Full | Yes | Yes | Yes* |
| Partial | No | No | Yes** |
*NOT RECOMMENDED. **From full nodes only.
Node profiles MUST conform to the following JSON schema and requirements:
{
"$defs": {
"NodeType": {
"type": "string",
"enum": [
"FULL",
"PARTIAL"
]
}
},
"title": "Node Profile",
"type": "object",
"properties": {
"node_type": {
"$ref": "#/$defs/NodeType"
},
"base_url": {
"type": "string"
},
"provides": {
"type": "object",
"properties": {
"event": {
"type": "array",
"items": {
"type": "string"
}
},
"state": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"event",
"state"
]
},
"public_key": {
"type": "string"
}
},
"required": [
"node_type",
"provides",
"public_key"
]
}
node_type MUST be either FULL or PARTIAL.base_url MUST be set to a valid URL for full nodes. It SHOULD end with /koi-net, and MUST NOT end with a trailing slash. It SHOULD be omitted for partial nodes.provides.event field MUST be an array of valid RID types. It MAY be empty.provides.state field MUST be an array of valid RID types. It MAY be empty.public_key MUST be encoded from a valid public key using the Subject Public Key Info (SPKI) format with Distinguished Encoding Rules (DER), with the resulting binary data further encoded in Base64.In a KOI network, edges represent event subscriptions, or directed communication pathways between nodes. Events are sent from the source node (or “publisher”), to the target node (or “subscriber”). Generally, edges SHOULD be proposed by the subscriber, indicating to a publisher node that they would like to receive events of the requested RID types.
Edges are identified using RIDs. Edge RIDs are Object Reference Names (ORNs) in the “KOI-net edge” namespace, with the RID type: orn:koi-net.edge. The reference component is the SHA-256 hash of the source and target RIDs. Edge RIDs are deterministically generated for a given source and target node pairing, therefore every possible edge will have a single valid identifier.
Edge RIDs MUST be constructed according to the following pattern and requirements:
orn:koi-net.edge:<hash>
hash MUST be the hex digest of the SHA-256 hash of the source and target nodes concatenated together, i.e., source RID + target RID.An edge profile describes important metadata about an edge, in a network: an edge’s type (webhook or poll), its source and target node RIDs, its status (proposed or approved), and the RID types the event subscription applies to. This is the data object that an edge RID refers to; thus, the contents of an edge RID bundle MUST be a valid edge profile.
This protocol defines two types of edges, indicating how events should be sent from the source to the target node:
Full node targets SHOULD use webhooks, allowing real-time transmission of knowledge objects without the delay of a polling loop. Partial node targets MUST use polling because they do not implement the broadcast events method. Possible edge configurations are summarized below:
| Edge Configuration (source -> target) | Webhooks supported | Polling supported |
|---|---|---|
| Full node -> full node | Yes | Yes* |
| Partial node -> full node | Yes | No |
| Full node -> partial node | No | Yes |
| Partial node -> partial node | No | No |
*NOT RECOMMENDED.
Edge profiles MUST conform to the following JSON schema and requirements:
{
"type": "object",
"properties": {
"edge_type": {
"type": "string",
"enum": [
"WEBHOOK",
"POLL"
]
},
"source": {
"type": "string",
"description": "RID of source node"
},
"target": {
"type": "string",
"description": "RID of target node"
},
"status": {
"type": "string",
"enum": [
"PROPOSED",
"APPROVED"
]
},
"rid_types": {
"type": "array",
"description": "The RID types that the source node will send the target node events about"
}
},
"required": [
"edge_type",
"source",
"target",
"status",
"rid_types"
]
}
edge_type MUST be either WEBOOK or PARTIAL.source and target MUST both be valid orn:koi-net.node RIDs.status MUST be either PROPOSED or APPROVED.rid_types field MUST be an array of valid RID types. It SHOULD NOT be empty.There are five methods defined across two classes of communication:
Communication occurs over the HTTP protocol. Future versions of this protocol may add support for additional transport layers. Nodes MUST use HTTPS when deployed on a public network.
The communication patterns in this section, along with the protocol objects, can be understood as a progressive disclosure pattern for knowledge exchange. The API methods correspond to each disclosure step, allowing nodes to access the minimum information needed to make a decision before requesting additional context.
flowchart LR
classDef core fill:#e3f2fd,stroke:#1565c0,stroke-width:2px;
classDef question fill:#f5f5f5,stroke:#9e9e9e,stroke-dasharray:4 3;
A["Reference Identifier (RID)"] --> B[Manifest]
B --> C[Bundle]
C --> D[Event]
QA["What exists?"] -.-> A
QB["What version?"] -.-> B
QC["What content?"] -.-> C
QD["What changed?"] -.-> D
class A,B,C,D core;
class QA,QB,QC,QD question;
Figure 2: Progressive disclosure pattern in node communication.
All methods MUST be called via POST request with a JSON body, and will receive a JSON response in return, with the exception of the broadcast events method, which will return an empty body. Requests MUST include a Content-Type: application/json header. Each method defines its endpoint as a relative path of the form /<object>/<action>, which is appended to a node’s base URL to form the full request URL. When responding to a valid request, the HTTP response status code SHOULD be 200, unless returning an error response (see [[#4.3. Error Responses]]).
Full nodes MUST implement all methods defined in this section.
In this section, the (full or partial) node initiating a request shall be called “the requester”, and the (full) node responding to a request shall be called “the responder”.
The broadcast events method allows the requester to transmit events directly to the responder. This method MUST be used for event transmission to the target node in WEBHOOK edges. The relative path of the broadcast events endpoint is /events/broadcast.
Requests MUST conform to the following JSON schema:
{
"title": "Events Payload",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "events_payload"
},
"events": {
"type": "array",
"items": {
"$ref": "./event.schema.json"
}
}
},
"required": [
"type",
"events"
]
}
Responses MUST contain an empty body.
The poll events method allows the requester to poll events from the responder. This method MUST be used for event transmission to the target node in POLL edges, and SHOULD only be used by partial nodes. The identity of the requester is determined by the source of the envelope. When the responder wishes to transmit events to the requester (a partial node or target node in a POLL edge), the events are held in an internal buffer until polled. The relative path of the broadcast events endpoint is /events/poll.
Requests MUST conform to the following JSON schema and requirements:
{
"title": "Poll Events",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "poll_events"
},
"limit": {
"type": "integer"
}
},
"required": [
"type"
]
}
limit field MAY be omitted, or set to 0, in which case all available events SHOULD be sent. Otherwise, the responder MUST send at most limit events.Responses MUST conform to the following JSON schema:
{
"title": "Events Payload",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "events_payload"
},
"events": {
"type": "array",
"items": {
"$ref": "./event.schema.json"
}
}
},
"required": [
"type",
"events"
]
}
The fetch RIDs method allows the requester to fetch a list of known RIDs from the responder. It is up to the responder to decide what to return, and it may decide to omit or include specific RIDs based on the identity of the requester. Generally, the responder SHOULD only return RIDs for knowledge objects that it provides access to via the fetch bundles method. The relative path of the broadcast events endpoint is /rids/fetch.
Requests MUST conform to the following JSON schema and requirements:
{
"title": "Fetch RIDs",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "fetch_rids"
},
"rid_types": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"type"
]
}
rid_types field MUST contain only valid RID types.rid_types. If rid_types is omitted or empty, RIDs of any type MAY be sent.Responses MUST conform to the following JSON schema and requirements:
{
"title": "RIDs Payload",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "rids_payload"
},
"rids": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"type",
"rids"
]
}
rids field MUST contain only valid RIDs.The fetch manifests method allows the requester to fetch a list of known manifests from the responder. It is up to the responder to decide what to return, and it may decide to include or omit specific manifests based on the identity of the requester. Generally, the responder SHOULD only return manifests for knowledge objects that it provides access to via the fetch bundles method. The relative path of the broadcast events endpoint is /manifests/fetch.
Requests MUST conform to the following JSON schema and requirements:
{
"title": "Fetch Manifests",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "fetch_manifests"
},
"rid_types": {
"type": "array",
"items": {
"type": "string"
}
},
"rids": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"type"
]
}
rids and rid_types SHOULD be set.rids field MUST contain only valid RIDs.rid_types field MUST contain only valid RID types.rid_types. If rid_types is omitted or empty, manifests with any RID type MAY be sent.rids. If rids is omitted or empty, manifests with any RID MAY be sent.Responses MUST conform to the following JSON schema and requirements:
{
"title": "Manifests Payload",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "manifests_payload"
},
"manifests": {
"type": "array",
"items": {
"$ref": "./manifest.schema.json"
}
},
"not_found": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"type",
"manifests"
]
}
manifests field is REQUIRED, but MAY be an empty array.not_found field.The fetch bundles method allows the requester to fetch a list of known bundles from the responder. It is up to the responder to decide what to return, and it may decide to include or omit specific bundle based on the identity of the requester. The relative path of the broadcast events endpoint is /bundles/fetch.
Requests MUST conform to the following JSON schema and requirements:
{
"title": "Fetch Bundles",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "fetch_bundles"
},
"rids": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"type",
"rids"
]
}
rids field is REQUIRED and MUST contain only valid RIDs.rids.Responses MUST conform to the following JSON schema and requirements:
{
"title": "Bundles Payload",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "bundles_payload"
},
"bundles": {
"type": "array",
"items": {
"$ref": "./bundle.schema.json"
}
},
"not_found": {
"type": "array",
"items": {
"type": "string"
}
},
"deferred": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"type",
"bundles"
]
}
bundles field is REQUIRED, but MAY be an empty array.not_found and deferred fields MUST contain only valid RIDs.not_found field.deferred field to indicate that the bundles of those RIDs are not immediately available, but will be transmitted as events to the requester once ready.The secure identity layer for the KOI-net protocol is built on public-private key cryptographic signatures and validation. Each node MUST maintain key pairs, sharing its public key in its node profile, and keeping its private key secret for signing its own communications.
Nodes MUST use the ECDSA P-256 (a.k.a. SECP256R1) elliptic curve algorithm.
Signed envelopes wrap both the request and response objects described in the API methods to provide secure identity validation during communication. Each signed envelope is composed of a payload, a source node, a target node, and a signature. The payload MUST be a valid request or response object defined in [[#4.1. API Methods]].
Signed envelopes MUST conform to the following JSON schema and requirements:
{
"title": "Signed Envelope",
"type": "object",
"properties": {
"payload": {
"anyOf": [
{"$ref": "./poll_events.schema.json"},
{"$ref": "./fetch_rids.schema.json"},
{"$ref": "./fetch_manifests.schema.json"},
{"$ref": "./fetch_bundles.schema.json"},
{"$ref": "./rids_payload.schema.json"},
{"$ref": "./manifests_payload.schema.json"},
{"$ref": "./bundles_payload.schema.json"},
{"$ref": "./events_payload.schema.json"}
]
},
"source_node": {
"type": "string"
},
"target_node": {
"type": "string"
},
"signature": {
"type": "string"
}
},
"required": [
"payload",
"source_node",
"target_node",
"signature"
]
}
source_node and target_node MUST be valid orn:koi-net.node RIDs.signature MUST be a valid Base64 encoding of the cryptographic signature.Unsigned envelopes are intermediary objects used for signature validation. To prevent unauthorized forwarding of communication to unintended target nodes, the source and target node must be included in the signed data. As a result, unsigned envelopes are identical to signed envelopes with the exception of the signature field.
Unsigned envelopes MUST conform to the following JSON schema and requirements:
{
"title": "Unsigned Envelope",
"type": "object",
"properties": {
"payload": {
"anyOf": [
{"$ref": "./poll_events.schema.json"},
{"$ref": "./fetch_rids.schema.json"},
{"$ref": "./fetch_manifests.schema.json"},
{"$ref": "./fetch_bundles.schema.json"},
{"$ref": "./rids_payload.schema.json"},
{"$ref": "./manifests_payload.schema.json"},
{"$ref": "./bundles_payload.schema.json"},
{"$ref": "./events_payload.schema.json"}
]
},
"source_node": {
"type": "string"
},
"target_node": {
"type": "string"
}
},
"required": [
"payload",
"source_node",
"target_node"
]
}
source_node and target_node MUST be valid orn:koi-net.node RIDs.When the requester sends a request or the responder sends a response, that communication object is wrapped in a signed envelope. First, an unsigned envelope is created, for which the payload is the request or response object, the source is the node sending the request or response, and the target is the node receiving the request or response.
The unsigned envelope MUST be serialized to a JSON string with no whitespace (e.g., JSON.stringify()). The resulting string MUST be encoded as bytes using UTF-8 encoding, and the resulting byte sequence signed using ECDSA with SHA-256. The resulting signature MUST be encoded as a raw signature of the format r||s, i.e., the concatenation of the scalar values r and s as a fixed length big endian byte sequence. Finally, the signature byte sequence MUST be encoded in Base64.
A signed envelope object is created from the unsigned envelope, with the signature set to the Base64 string described above. This envelope is the JSON object that becomes the HTTP request or response body.
Envelopes are verified on receipt, before processing, by both requesters and responders. If the source node is known to the target node at the time of receiving an envelope, the following requirements MUST be met before processing the communication:
Requirements are evaluated in order. If any requirement is not met, evaluation MUST be terminated immediately, and the envelope discarded. If a responder determines an envelope is invalid, the appropriate error response SHOULD be returned to the requester.
If the source node is not known to the target node at the time of receiving a request, responders MAY still process the communication if the following requirements are met:
NEW node profile bundle for the source node’s RID.Requirements are evaluated in order. If any requirement is not met, evaluation MUST be terminated immediately, and the envelope discarded. If a responder determines an envelope is invalid, the appropriate error response SHOULD be returned to the requester.
flowchart TD
start([Receive signed envelope]) --> nodeKnown{"Source node known?"}
nodeKnown -- No --> eventsBroadcast{"Envelope payload is an events broadcast?"}
nodeKnown -- Yes --> loadProfile["Load node profile from cache"]
eventsBroadcast -- No --> unknownNode(["Error: unknown_node"])
eventsBroadcast -- Yes --> newProfile{"Contains NEW event for source node's node profile?"}
newProfile -- No --> unknownNode
newProfile -- Yes --> cacheProfile["Use node profile from event"]
cacheProfile & loadProfile --> keyHash{"Public key hash matches source node RID?"}
keyHash -- No --> invalidKey(["Error: invalid_key"])
keyHash -- Yes --> signatureValid{"Signature verified by public key?"}
signatureValid -- No --> invalidSignature(["Error: invalid_signature"])
signatureValid -- Yes --> targetMe{"Target node is this node?"}
targetMe -- No --> invalidTarget(["Error: invalid_target"])
targetMe -- Yes --> success(["Signed envelope validated!"])
classDef process fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
classDef decision fill:#fff9c4,stroke:#fbc02d,stroke-width:2px;
classDef error fill:#ffcdd2,stroke:#c62828,stroke-width:2px;
classDef success fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px;
class loadProfile,cacheProfile,start process;
class nodeKnown,eventsBroadcast,newProfile,keyHash,signatureValid,targetMe decision;
class success success;
class unknownNode,invalidKey,invalidSignature,invalidTarget error;
Figure 3: Flowchart of communication validation process for both known and unknown nodes.
Under certain circumstances the responder MAY return an error response instead of the response objects described in the API methods. Unlike response objects, error responses are not wrapped in an envelope or cryptographically signed. When returning an error response, the HTTP response status code SHOULD be 400, indicating a client error. The requester is expected to validate the error response and determine the error type from the JSON body rather than relying on a specific status code.
There are four error types:
unknown_node: Indicates that the requester is not known to the responder, and their signature cannot be validated.invalid_key: Indicates that the public key in the requester’s node profile does not match the public hash key in their RID.invalid_signature: Indicates the signature in the signed envelope failed verification.invalid_target: Indicates that the target node in the signed envelope does not match the RID of the responder.Error responses MUST conform to the following JSON schema and requirements:
{
"title": "Error Response",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "error_response"
},
"error": {
"type": "string",
"enum": [
"unknown_node",
"invalid_key",
"invalid_signature",
"invalid_target"
]
}
},
"required": [
"type",
"error"
]
}
Node behaviors are standardized higher-level communication patterns that may involve successive communication according to special rules.
A node handshake is the process by which two nodes exchange their node profiles through peer-to-peer communication. This process establishes mutual awareness and public key exchange for further secure communication. During a handshake, two nodes take on the roles of:
To start a handshake, the initiator MUST broadcast its node profile bundle to the responder as a NEW event. Therefore, the initiator MUST already know the responder’s RID and URL to begin the handshake. Generally, this is a preconfigured “first contact” that is set before running a node.
The responder MAY respond by broadcasting its own node profile bundle to the initiator as a NEW event. If the responder does not want to complete the handshake it MAY simply ignore the request. For a variety of reasons (e.g., network architecture, security concerns), nodes may not respond to a handshake, or even implement responder behavior.
If a node implements handshake responder behavior, the following conditions MUST be met before treating an event as a handshake:
NEW.[[#4.2.5. Verifying Communication with Unknown Nodes]] is intended to allow handshakes to occur with the same strong secure identity guarantees, even when the initiator is unknown to the responder at the time of handshake.
Edge negotiation is the process by which two nodes reach consensus on an event subscription between them. This is represented as a directed edge in a graph, where the source is the publisher and the target is the subscriber.
To begin edge negotiation, the subscriber MUST broadcast an edge profile bundle to the publisher as a NEW or UPDATE event. A NEW event SHOULD be used when the proposed edge did not previously exist. An UPDATE event SHOULD be used when the proposed edge is an update to an existing edge (e.g., amending the list of subscribed RID types). The fields of the edge profile MUST be set accordingly:
source MUST be the subscriber’s RID.target MUST be the publisher’s RID.edge_type SHOULD be WEBHOOK for full node subscribers, and MUST be POLL for partial node subscribers.status MUST be PROPOSED.rid_types SHOULD contain only RID types that the publisher provides in its node profile, or koi-net.node or koi-net.edge, which are implicitly provided by all nodes.The publisher MAY approve the edge by updating the status to APPROVED and transmitting the updated edge profile bundle to the subscriber.
If the subscriber’s proposed edge includes RID types that the publisher does not provide, or the subscriber’s proposed edge type is WEBHOOK but the subscriber’s node type is PARTIAL, then the publisher SHOULD reject it by broadcasting a FORGET event to the subscriber. To avoid proposing an invalid edge, a subscriber SHOULD set the edge_type based on its own node type, and check a publisher’s node profile to see what RID types it provides.
An established edge may be closed at any time by the subscriber or publisher broadcasting a FORGET event to the other node.