npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kinnet/protocol

v0.7.2

Published

Shared protocol types and Zod schemas for the Kinnet participant network: records, key-event logs, claims, relationships, grants, and revocations.

Readme

@kinnet/protocol

The record types and Zod schemas of the Participant Network — the shared substrate every other Kinnet package consumes. No crypto, no I/O: this package decides what a record's fields are and whether a candidate value is shape-valid, and nothing about whether it is signed correctly or who to trust. @kinnet/crypto signs and verifies against these shapes; @kinnet/trust and @kinnet/verify resolve identity and authority from them.

Install

npm install @kinnet/protocol

or from a checkout of the repository:

pnpm install && pnpm build

Parse and validate a record

Every record kind is a Zod schema. safeParse returns a typed result instead of throwing, so a caller reading untrusted bytes decides what "invalid" means for its own surface:

import { participantProfileSchema, type ParticipantProfile } from "@kinnet/protocol";

const candidate = {
  id: "pk_zQmYwAPJzv5CZsnAzt8auVZRnHEKzKgUEdy3W35nUSpS6kq",
  type: "agent",
  displayName: "Ordering Agent",
  capabilities: ["orders/create"],
  verifiedDomains: [],
  updatedAt: "2026-08-17T00:00:00.000Z",
  signature:
    "z5rnVTAbdGrtjnu47AiPSJ3rr9iDskEANf6PLXXbEN6tfkP43izwMaLyBii1ZyqGYGdLVTXEBssdja39ZAqoXdjFx"
};

const result = participantProfileSchema.safeParse(candidate);
if (result.success) {
  const profile: ParticipantProfile = result.data;
  console.log(profile.displayName);
} else {
  console.error(result.error.issues);
}

This schema only checks shape — field types, formats, the cross-field rules the record's spec states. It says nothing about whether signature actually verifies against the claimed id's current key; that is @kinnet/crypto's verifyRecord against a key state resolved by replaying the id's key log.

The signed records are participantProfileSchema, participantNodeSchema, keyEventSchema / keyEventLogSchema, claimSchema, relationshipSchema, grantSchema, revocationSchema, messageEnvelopeSchema, conversationSchema, and conversationUpdateSchema — each exports its inferred TypeScript type alongside it (ParticipantProfile, Grant, KeyEvent, …).

The strictness rule

Every signed record schema is a Zod strictObject: a value carrying a key the schema does not define is rejected, not silently stripped. That matters because these records are digest- or signature-identified — a permissive parser that drops an unknown key would let one delivered byte string decode into two different logical records, one with the key and one without, both claiming the same signature:

import { participantProfileSchema } from "@kinnet/protocol";

const withStrayKey = {
  id: "pk_zQmYwAPJzv5CZsnAzt8auVZRnHEKzKgUEdy3W35nUSpS6kq",
  type: "agent",
  displayName: "Ordering Agent",
  capabilities: [],
  verifiedDomains: [],
  updatedAt: "2026-08-17T00:00:00.000Z",
  signature:
    "z5rnVTAbdGrtjnu47AiPSJ3rr9iDskEANf6PLXXbEN6tfkP43izwMaLyBii1ZyqGYGdLVTXEBssdja39ZAqoXdjFx",
  extra: "not part of the schema"
};

participantProfileSchema.safeParse(withStrayKey).success; // false — "Unrecognized key: extra"

Claim and Relationship are strict for the same reason and against each other specifically: open schemas that each strip what they don't define would let one object parse as both kinds under one signature. Two record kinds must never both accept the same bytes.

The record anchor

Revocation, Grant, Conversation and ConversationUpdate are verified against a participant's key state, and each names the state it was signed under: anchor, the digest of one event in the issuer's key log (spec 016). A verifier resolves that one state and tries no other, which is what makes an edited signature set unmoveable onto a state that would accept it.

Where the field is required is a property of the issuer, and the schemas enforce it: a Revocation always carries one; a Grant carries one exactly when issuerId is a participant id and never when it is a bare KeyRef; a Conversation or ConversationUpdate carries one in owner mode and not in delegated mode, where the chain's leaf key is the only candidate. In a (record, chain) unit the two must agree — anchor present if and only if chain is absent — so the mode is decided by the unit rather than inferred from a failed verification.

Encoding helpers

Bytes that will become a record — an HTTP body, a stored blob, anything read from outside the process — need the same care before they reach a schema. decodeUtf8Strict refuses malformed UTF-8 instead of silently substituting U+FFFD (the default TextDecoder behavior, which makes two different byte strings decode to the same text), and parseJsonStrict refuses a JSON object that repeats a key at any depth, since different parsers resolve a duplicate key differently:

import { decodeUtf8Strict, parseJsonStrict } from "@kinnet/protocol";

const octets: Uint8Array =
  /* the exact bytes that were signed or delivered */ new TextEncoder().encode('{"want":"quote"}');
const record: unknown = parseJsonStrict(decodeUtf8Strict(octets));

Use this pair — never JSON.parse(new TextDecoder().decode(octets)) — anywhere a signature or a digest covers the delivered octets and the parsed value is about to be checked against a schema above. @kinnet/verify's discovery client and @kinnet/crypto's grant-chain header decoder both route through it.

This module also exports keyRefSchema, signatureSchema, multihashSchema, and participantIdSchema for validating identifiers and encoded values on their own, and the ability vocabulary (abilitySchema, isE2eeAbility) used by grant chains.

Conformance vectors

Where a record's bytes are digested or its kind must be distinguishable from every other kind, the schemas are backed by committed fixtures a third party can check without this package:

  • test/fixtures/record-kind-vectors.json — one shape-valid instance of every record and payload kind; each must validate under its own schema and be rejected by every other one (record kinds are non-confusable).
  • test/fixtures/signed-conversation.json, test/fixtures/signed-conversation-e2ee.json — a replayable key log paired with a signed Conversation record, machine lane and E2EE lane.
  • test/fixtures/conversation-update-vectors.json — accept/reject cases for conversationUpdateSchema's well-formedness rules (spec 014).
  • test/fixtures/conversation-unit-vectors.json — the (record, chain) payload wrapper accept/ reject cases, including spec 016's anchor/chain agreement, and the digest-identity property that the chain travels alongside the record without changing its id.
  • test/fixtures/commit-validity-vectors.json — spec 014's membership-change commit-validity rules (apply / wait / invalid) over full evidence sets.

Specs

The full index is at packages/protocol/spec.

Status

Pre-1.0, pre-wire-freeze: 0.x releases are for early adopters, and record shapes may still change between them. The wire freezes at 1.0, when the maintainers declare it — not before. Track the spec, not any one version of this package — the protocol is meant to be implemented independently, and the conformance vectors are the compatibility contract.

License

Apache-2.0