@joe-broadhead/messaging 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +259 -2
- package/dist/bin/messaging-mcp.js +5484 -3858
- package/dist/bin/messaging-relay.js +4728 -1136
- package/dist/bin/messaging.js +4616 -2807
- package/dist/bin/messagingd.js +16451 -9444
- package/dist/lib/modules/core/agent/operations.js +19 -327
- package/dist/lib/modules/core/agent/present.js +15 -55
- package/dist/lib/modules/core/agent-tools.js +46 -143
- package/dist/lib/modules/core/consent.js +1 -1
- package/dist/lib/modules/core/conversation/context.js +80 -0
- package/dist/lib/modules/core/conversation/event.js +125 -0
- package/dist/lib/modules/core/conversation/guest-invitation.js +42 -0
- package/dist/lib/modules/core/conversation/ids.js +38 -0
- package/dist/lib/modules/core/conversation/link.js +35 -0
- package/dist/lib/modules/core/conversation/log.js +988 -0
- package/dist/lib/modules/core/conversation/ratchet.js +243 -0
- package/dist/lib/modules/core/conversation/request-log.js +158 -0
- package/dist/lib/modules/core/conversation/request-seal.js +41 -0
- package/dist/lib/modules/core/conversation/requests.js +87 -0
- package/dist/lib/modules/core/conversation/seal.js +251 -0
- package/dist/lib/modules/core/conversation-agent-tools.js +69 -0
- package/dist/lib/modules/core/federation/directory-proof.js +83 -0
- package/dist/lib/modules/core/federation/proof-state.js +167 -0
- package/dist/lib/modules/core/federation/roster-proof.js +74 -0
- package/dist/lib/modules/core/index.js +16 -2
- package/dist/lib/modules/core/jelly-host.js +5 -5
- package/dist/lib/modules/core/schema/common.js +85 -3
- package/dist/lib/modules/core/schema/consent.js +5 -4
- package/dist/lib/modules/core/schema/contact-request.js +22 -0
- package/dist/lib/modules/core/schema/conv-frames.js +94 -0
- package/dist/lib/modules/core/schema/conversation.js +326 -0
- package/dist/lib/modules/core/schema/envelope.js +8 -24
- package/dist/lib/modules/core/schema/errors.js +42 -2
- package/dist/lib/modules/core/schema/guest-invitation.js +13 -0
- package/dist/lib/modules/core/schema/index.js +6 -0
- package/dist/lib/modules/core/schema/ipc/consent.js +13 -9
- package/dist/lib/modules/core/schema/ipc/conversation-views.js +80 -0
- package/dist/lib/modules/core/schema/ipc/conversations.js +128 -0
- package/dist/lib/modules/core/schema/ipc/device.js +2 -0
- package/dist/lib/modules/core/schema/ipc/jelly.js +3 -38
- package/dist/lib/modules/core/schema/ipc/shapes.js +14 -72
- package/dist/lib/modules/core/schema/ipc/surfaces.js +12 -195
- package/dist/lib/modules/core/schema/ipc/tasks.js +31 -0
- package/dist/lib/modules/core/schema/ipc.js +18 -44
- package/dist/lib/modules/core/testing.js +1 -0
- package/dist/lib/modules/core/version.js +1 -1
- package/dist/lib/modules/ipc/client.js +10 -2
- package/dist/lib/modules/ipc/server.js +14 -5
- package/dist/lib/modules/ipc/testing.js +13 -23
- package/dist/spec/messaging-v1.md +835 -380
- package/dist/third-party-notices.md +1 -1
- package/dist/third-party.json +1 -1
- package/dist/types/core/agent/operations.d.ts +5 -52
- package/dist/types/core/agent/present.d.ts +6 -21
- package/dist/types/core/agent-tools.d.ts +277 -438
- package/dist/types/core/conversation/context.d.ts +56 -0
- package/dist/types/core/conversation/event.d.ts +36 -0
- package/dist/types/core/conversation/guest-invitation.d.ts +8 -0
- package/dist/types/core/conversation/ids.d.ts +18 -0
- package/dist/types/core/conversation/link.d.ts +16 -0
- package/dist/types/core/conversation/log.d.ts +157 -0
- package/dist/types/core/conversation/ratchet.d.ts +95 -0
- package/dist/types/core/conversation/request-log.d.ts +24 -0
- package/dist/types/core/conversation/request-seal.d.ts +6 -0
- package/dist/types/core/conversation/requests.d.ts +14 -0
- package/dist/types/core/conversation/seal.d.ts +70 -0
- package/dist/types/core/conversation-agent-tools.d.ts +918 -0
- package/dist/types/core/federation/directory-proof.d.ts +8 -0
- package/dist/types/core/federation/proof-state.d.ts +114 -0
- package/dist/types/core/federation/roster-proof.d.ts +7 -0
- package/dist/types/core/index.d.ts +15 -3
- package/dist/types/core/jelly-host.d.ts +1 -1
- package/dist/types/core/schema/common.d.ts +85 -4
- package/dist/types/core/schema/consent.d.ts +7 -7
- package/dist/types/core/schema/contact-request.d.ts +264 -0
- package/dist/types/core/schema/conv-frames.d.ts +1200 -0
- package/dist/types/core/schema/conversation.d.ts +2803 -0
- package/dist/types/core/schema/envelope.d.ts +89 -130
- package/dist/types/core/schema/errors.d.ts +40 -0
- package/dist/types/core/schema/guest-invitation.d.ts +51 -0
- package/dist/types/core/schema/index.d.ts +6 -0
- package/dist/types/core/schema/ipc/consent.d.ts +89 -49
- package/dist/types/core/schema/ipc/conversation-views.d.ts +1023 -0
- package/dist/types/core/schema/ipc/conversations.d.ts +3987 -0
- package/dist/types/core/schema/ipc/device.d.ts +10 -0
- package/dist/types/core/schema/ipc/jelly.d.ts +2 -105
- package/dist/types/core/schema/ipc/shapes.d.ts +16 -95
- package/dist/types/core/schema/ipc/surfaces.d.ts +28 -424
- package/dist/types/core/schema/ipc/tasks.d.ts +208 -0
- package/dist/types/core/schema/ipc.d.ts +2684 -523
- package/dist/types/core/schema/relay.d.ts +20 -4
- package/dist/types/core/testing.d.ts +1 -0
- package/dist/types/core/version.d.ts +1 -1
- package/dist/types/ipc/client.d.ts +4 -1
- package/dist/types/ipc/server.d.ts +10 -1
- package/dist/types/ipc/testing.d.ts +21 -8
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { PersonId, SessionName } from "../schema/common.js";
|
|
2
|
+
import { type AgentContext, type ContextAccess, type ContextKind, type ContextRule, type ConvId, type ConvKind, type Visibility } from "../schema/conversation.js";
|
|
3
|
+
/** The defaults that apply where a person has no rule (decided 2026-11-08). `lookbackDays` absent: the retention. */
|
|
4
|
+
export declare const CONTEXT_DEFAULTS: Readonly<Record<ContextKind, {
|
|
5
|
+
access: ContextAccess;
|
|
6
|
+
lookbackDays?: number;
|
|
7
|
+
}>>;
|
|
8
|
+
/** The facts about a conversation that resolution reads. `persons` are its participant persons at the read. */
|
|
9
|
+
export interface ContextConv {
|
|
10
|
+
cid: ConvId;
|
|
11
|
+
kind: ConvKind;
|
|
12
|
+
visibility?: Visibility;
|
|
13
|
+
persons: readonly PersonId[];
|
|
14
|
+
retainDays: number;
|
|
15
|
+
agentContext: AgentContext;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The scope a conversation falls in for `person`: `own` when its participant persons are `person` alone, else its
|
|
19
|
+
* kind (a channel by its visibility). A `self` conversation has none: no agent mines it.
|
|
20
|
+
*/
|
|
21
|
+
export declare function contextKindOf(person: PersonId, conv: Pick<ContextConv, "kind" | "visibility" | "persons">): ContextKind | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* The person's rule for (agent, conversation), most specific first: a `cid` scope over a `kind` scope, then a named
|
|
24
|
+
* agent over `*`. Undefined when no rule matches; the defaults then apply.
|
|
25
|
+
*/
|
|
26
|
+
export declare function matchContextRule(rules: readonly ContextRule[], agent: SessionName, cid: ConvId, kind: ContextKind): ContextRule | undefined;
|
|
27
|
+
/** The outcome of a context read: served, held for the person (`ask`), or refused. */
|
|
28
|
+
export type ContextDecision = {
|
|
29
|
+
access: "allow" | "ask";
|
|
30
|
+
lookbackDays: number;
|
|
31
|
+
source: "rule" | "default";
|
|
32
|
+
} | {
|
|
33
|
+
access: "forbidden";
|
|
34
|
+
source: "policy" | "rule" | "self";
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The effective rule for agent `agent` of `person` reading `conv` as context (not by its seat):
|
|
38
|
+
* - the conversation's policy `seats` or `none` forbids it, whatever the person's rule says;
|
|
39
|
+
* - else the person's rule, or the default: `off` forbids, `ask` holds, `allow` serves;
|
|
40
|
+
* - the lookback is the smallest of the rule's (or the default's) `lookbackDays`, the conversation's `retainDays`,
|
|
41
|
+
* and the person's local retention for it.
|
|
42
|
+
* Throws `invalid_request` for a rule set that fails `ContextRules`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function resolveContext(args: {
|
|
45
|
+
person: PersonId;
|
|
46
|
+
agent: SessionName;
|
|
47
|
+
conv: ContextConv;
|
|
48
|
+
rules: readonly ContextRule[];
|
|
49
|
+
localRetentionDays?: number;
|
|
50
|
+
}): ContextDecision;
|
|
51
|
+
/**
|
|
52
|
+
* What a seated agent reads by its seat (spec §16.8): everything from its seat's `seq` on, unless the policy is
|
|
53
|
+
* `none`, when it reads only the events addressed to it and the threads they are in. The person's rules do not
|
|
54
|
+
* apply to seat reads.
|
|
55
|
+
*/
|
|
56
|
+
export declare function seatReads(agentContext: AgentContext): "conversation" | "items";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Identity } from "../identity.js";
|
|
2
|
+
import { type Hash } from "../schema/common.js";
|
|
3
|
+
import { type ConvAnyEvent, type ConvEventHeader, type ConvId, type UnsignedConvEvent } from "../schema/conversation.js";
|
|
4
|
+
/** h(0) of `cid`'s log: SHA-256(DOMAIN.convGenesis ‖ JCS(cid)), base64url. */
|
|
5
|
+
export declare function convGenesisHash(cid: ConvId): Hash;
|
|
6
|
+
/** SHA-256(JCS(event)), the raw digest, as the chain links it. Throws `invalid_request` for non-I-JSON. */
|
|
7
|
+
export declare function convEventDigest(event: unknown): Uint8Array;
|
|
8
|
+
/** h(seq) = SHA-256(h(seq − 1) ‖ SHA-256(JCS(event))), over the raw 32-byte digests, base64url. */
|
|
9
|
+
export declare function convEntryHash(prev: Hash, event: unknown): Hash;
|
|
10
|
+
/** `convEntryHash` from the event's digest: what a member checks for an entry whose content has expired. */
|
|
11
|
+
export declare function convLinkHash(prev: Hash, digest: Uint8Array): Hash;
|
|
12
|
+
/** The header of an event: everything but `body` and `sig`. JCS of it is the AAD of a sealed body. */
|
|
13
|
+
export declare function convEventHeader(event: ConvAnyEvent | Omit<ConvAnyEvent, "sig"> | ConvEventHeader): ConvEventHeader;
|
|
14
|
+
/**
|
|
15
|
+
* Checks that `raw` is I-JSON within `LIMITS.jsonDepth` and at most `LIMITS.convEventBytes` (`too_large`), that it
|
|
16
|
+
* matches `ConvAnyEvent` (an op, a msg or a key share), and that `by.device` is a usable Ed25519 key. Returns a frozen JSON copy of the raw value, so
|
|
17
|
+
* the signature and the hash cover exactly what was received. The signature is not checked.
|
|
18
|
+
*/
|
|
19
|
+
export declare function admitConvEvent(raw: unknown): ConvAnyEvent;
|
|
20
|
+
/**
|
|
21
|
+
* Signs an event (spec §16.4): `sig = Ed25519(by.device, DOMAIN.convEvent ‖ JCS(event without sig))`. `by.device`
|
|
22
|
+
* must be `identity`'s machine. Refuses an event that fails the schema (`invalid_request`) or is over
|
|
23
|
+
* `LIMITS.convEventBytes` once signed (`too_large`). Authority is checked when the event is applied.
|
|
24
|
+
*/
|
|
25
|
+
export declare function signConvEvent<E extends UnsignedConvEvent>(identity: Identity, unsigned: E): E & {
|
|
26
|
+
sig: string;
|
|
27
|
+
};
|
|
28
|
+
/** True only for a valid signature by `event.by.device` over the event without `sig`. Never throws. */
|
|
29
|
+
export declare function convEventSigned(event: ConvAnyEvent): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Admits `raw` (`admitConvEvent`) and checks its signature over the raw value (`bad_signature`). Membership and
|
|
32
|
+
* authority are `ConvLog.apply`'s. Throws only MessagingError.
|
|
33
|
+
*/
|
|
34
|
+
export declare function verifyConvEvent(raw: unknown): ConvAnyEvent;
|
|
35
|
+
/** Freezes a JSON value and everything in it, iteratively. */
|
|
36
|
+
export declare function deepFreeze<T>(value: T): T;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Identity } from "../identity.js";
|
|
2
|
+
import { GuestInvitation } from "../schema/guest-invitation.js";
|
|
3
|
+
import type { ConvLog } from "./log.js";
|
|
4
|
+
/** The inviting device seals this preview to the invitee. No history or sender keys accompany it. */
|
|
5
|
+
export declare function createGuestInvitation(log: ConvLog, to: string, identity: Identity, issuedAt: number, expiresAt: number): GuestInvitation;
|
|
6
|
+
/** The accept event signs this same preview. Changed audience/policy/head requires a fresh preview. */
|
|
7
|
+
export declare function verifyGuestInvitation(log: ConvLog, raw: unknown, to: string, at: number): GuestInvitation;
|
|
8
|
+
export declare function guestHistoryExposure(history: "none" | "shared"): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PersonId } from "../schema/common.js";
|
|
2
|
+
import { type ConvId, ParticipantId } from "../schema/conversation.js";
|
|
3
|
+
/**
|
|
4
|
+
* The id of the dm between `a` and `b`: `"d_" + base32lower(SHA-256(DOMAIN.dm ‖ JCS([a, b] sorted)))[0..26]`. Both
|
|
5
|
+
* ends derive the same id, so a dm has no create race. `a` may equal `b`: a person's note to themselves.
|
|
6
|
+
*/
|
|
7
|
+
export declare function dmConvId(a: ParticipantId, b: ParticipantId): ConvId;
|
|
8
|
+
/** The id of `person`'s own `self` conversation: `"s_" + base32lower(SHA-256(DOMAIN.self ‖ JCS(person)))[0..26]`. */
|
|
9
|
+
export declare function selfConvId(person: PersonId): ConvId;
|
|
10
|
+
/** The person a participant is, or belongs to. */
|
|
11
|
+
export declare function participantPerson(p: ParticipantId): PersonId;
|
|
12
|
+
/** The agent's session name, for an agent participant. */
|
|
13
|
+
export declare function participantAgent(p: ParticipantId): string | undefined;
|
|
14
|
+
/** The participant an event's `by` names: `person`, or `person/agent`. */
|
|
15
|
+
export declare function participantOf(by: {
|
|
16
|
+
person: PersonId;
|
|
17
|
+
agent?: string | undefined;
|
|
18
|
+
}): ParticipantId;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MachineId, PersonId } from "../schema/common.js";
|
|
2
|
+
import { type LinkRecord, type MsgBody, type ReaderId } from "../schema/conversation.js";
|
|
3
|
+
/** The links in force: for each link id the record written last (records are in `seq` order), unless revoked. */
|
|
4
|
+
export declare function currentLinks(records: readonly LinkRecord[]): LinkRecord[];
|
|
5
|
+
/**
|
|
6
|
+
* Checks a line's `via` against the author's links, as the author's own devices can (other members cannot see the
|
|
7
|
+
* person's links): only a person writes from a linked channel, never an agent, and only through a link of that
|
|
8
|
+
* channel that the authoring device hosts and that is in force. Throws `forbidden`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function checkVia(body: MsgBody, by: {
|
|
11
|
+
person: PersonId;
|
|
12
|
+
device: MachineId;
|
|
13
|
+
agent?: string;
|
|
14
|
+
}, links: readonly LinkRecord[]): void;
|
|
15
|
+
/** The reader id of a link's channel provider, for the audience guard: `ext:<channel>:<link id>`. */
|
|
16
|
+
export declare function linkReader(link: Pick<LinkRecord, "channel" | "id">): ReaderId;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { AnchorRelation } from "../realm.js";
|
|
2
|
+
import { type Hash, type MachineId, type PersonId } from "../schema/common.js";
|
|
3
|
+
import { type AgentContext, type BridgePolicy, type ConvEvent, type ConvExpiredEntry, type ConvHead, type ConvId, type ConvKeysEvent, type ConvKind, type ConvLogEntry, type ConvRole, type HistoryPolicy, type MsgKind, type ParticipantId, type Permit, type RekeyCause, type RespondTo, type Visibility } from "../schema/conversation.js";
|
|
4
|
+
import type { RealmId } from "../schema/realm.js";
|
|
5
|
+
import type { RequestBinding } from "./requests.js";
|
|
6
|
+
/** A participant at one position. */
|
|
7
|
+
export interface ConvParticipant {
|
|
8
|
+
p: ParticipantId;
|
|
9
|
+
person: PersonId;
|
|
10
|
+
/** The agent's session name, for an agent participant. */
|
|
11
|
+
agent?: string;
|
|
12
|
+
role: ConvRole;
|
|
13
|
+
/** `invited` until the person accepts, or until the agent's person seats it. */
|
|
14
|
+
state: "member" | "invited";
|
|
15
|
+
device?: MachineId;
|
|
16
|
+
respondTo?: RespondTo;
|
|
17
|
+
/** The seq at which it became a participant. */
|
|
18
|
+
since: number;
|
|
19
|
+
}
|
|
20
|
+
/** Everything the log decides at one position. Frozen. */
|
|
21
|
+
export interface ConvSnapshot {
|
|
22
|
+
cid: ConvId;
|
|
23
|
+
kind: ConvKind;
|
|
24
|
+
visibility?: Visibility;
|
|
25
|
+
history: HistoryPolicy;
|
|
26
|
+
retainDays: number;
|
|
27
|
+
org?: string;
|
|
28
|
+
post: Permit;
|
|
29
|
+
invite: Permit;
|
|
30
|
+
pins: Permit;
|
|
31
|
+
archived: boolean;
|
|
32
|
+
epoch: number;
|
|
33
|
+
/** The seq and `ts` of the op that started the epoch. */
|
|
34
|
+
epochSince: number;
|
|
35
|
+
epochTs: number;
|
|
36
|
+
/** Causes that need a `rekey` before the next message (spec §16.7). Sorted. */
|
|
37
|
+
rekeyDue: RekeyCause[];
|
|
38
|
+
/** Sorted by `p`. */
|
|
39
|
+
participants: ConvParticipant[];
|
|
40
|
+
/** The effective policy. In a channel the owners' and admins'; in a dm or a group the strictest vote. */
|
|
41
|
+
agentContext: AgentContext;
|
|
42
|
+
/**
|
|
43
|
+
* Who may mirror the conversation to a linked channel (reserved: no daemon acts on it yet). In a channel the
|
|
44
|
+
* owners' and admins'; in a dm or a group the strictest vote, where a person who has not voted counts as `none`.
|
|
45
|
+
*/
|
|
46
|
+
bridges: BridgePolicy;
|
|
47
|
+
/** dm and group: each participant person's votes, sorted by person. */
|
|
48
|
+
votes: ConvVote[];
|
|
49
|
+
/** The seq of the entry that last changed any of the above. */
|
|
50
|
+
changed: number;
|
|
51
|
+
}
|
|
52
|
+
/** One participant person's policy votes in a dm or group. */
|
|
53
|
+
export interface ConvVote {
|
|
54
|
+
person: PersonId;
|
|
55
|
+
agentContext?: AgentContext;
|
|
56
|
+
bridges?: BridgePolicy;
|
|
57
|
+
}
|
|
58
|
+
/** What a member knows of one event, by id. */
|
|
59
|
+
export interface ConvMessage {
|
|
60
|
+
id: string;
|
|
61
|
+
seq: number;
|
|
62
|
+
class: ConvEvent["class"];
|
|
63
|
+
author: ParticipantId;
|
|
64
|
+
/** Known when the body was opened. */
|
|
65
|
+
t?: MsgKind;
|
|
66
|
+
deleted: boolean;
|
|
67
|
+
pinned: boolean;
|
|
68
|
+
reactions: {
|
|
69
|
+
key: string;
|
|
70
|
+
by: ParticipantId[];
|
|
71
|
+
}[];
|
|
72
|
+
/** For a task request: its executor. */
|
|
73
|
+
taskTo?: ParticipantId;
|
|
74
|
+
/** For a task request: true. */
|
|
75
|
+
taskRoot?: boolean;
|
|
76
|
+
}
|
|
77
|
+
/** Checks only a live append can make: they depend on the realm now, not on the log. */
|
|
78
|
+
export interface ConvLiveChecks {
|
|
79
|
+
/** Wall-clock time; the event's `ts` must be within `skewMs` (default `LIMITS.clockSkewMs`) of it (`expired`). */
|
|
80
|
+
now?: number;
|
|
81
|
+
skewMs?: number;
|
|
82
|
+
/** True when `device` is a current device of `person` in the realm (`unknown_device` otherwise). */
|
|
83
|
+
isDevice?: (person: PersonId, device: MachineId) => boolean;
|
|
84
|
+
/** True only when the verified realm has removed this device. */
|
|
85
|
+
isRemovedDevice?: (device: MachineId) => boolean;
|
|
86
|
+
/** Retained identity history for portable request proofs, including historical devices. */
|
|
87
|
+
requestBinding?: RequestBinding;
|
|
88
|
+
/** The org of `person`, for `join`, and for an add of a person who is not a guest (`forbidden`). */
|
|
89
|
+
orgOf?: (person: PersonId) => string | undefined;
|
|
90
|
+
}
|
|
91
|
+
export interface ConvApplyOptions {
|
|
92
|
+
/** The opened body of a `msg` entry; the message rules run only with it. */
|
|
93
|
+
body?: unknown;
|
|
94
|
+
live?: ConvLiveChecks;
|
|
95
|
+
}
|
|
96
|
+
/** Local admission metadata, never part of an entry's signed wire representation. */
|
|
97
|
+
interface ReplayEntry {
|
|
98
|
+
entry: unknown;
|
|
99
|
+
body?: unknown;
|
|
100
|
+
held?: boolean;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* A verified conversation log. Every method throws only MessagingError. Entries are kept as frozen copies of the
|
|
104
|
+
* received JSON, so hashes cover exactly what was received.
|
|
105
|
+
*/
|
|
106
|
+
export declare class ConvLog {
|
|
107
|
+
#private;
|
|
108
|
+
readonly cid: ConvId;
|
|
109
|
+
readonly realm: RealmId;
|
|
110
|
+
private constructor();
|
|
111
|
+
/** An empty log of `cid` in `realm`: its head is {0, h(0)}. */
|
|
112
|
+
static open(cid: ConvId, realm: RealmId): ConvLog;
|
|
113
|
+
/** Replays a prefix; callers with a verified realm supply its removed-device check for every device rekey. */
|
|
114
|
+
static fromEntries(cid: ConvId, realm: RealmId, entries: ReplayEntry[], checks?: Pick<ConvLiveChecks, "isRemovedDevice" | "requestBinding">, onInvalidBody?: (seq: number) => void): ConvLog;
|
|
115
|
+
head(): ConvHead;
|
|
116
|
+
/** The last op's seq and hash, or {0, h(0)}: what an author puts in `opHead`. */
|
|
117
|
+
opHead(): ConvHead;
|
|
118
|
+
/** h(seq), for 0 ≤ seq ≤ head. */
|
|
119
|
+
hashAt(seq: number): Hash | undefined;
|
|
120
|
+
entries(): (ConvLogEntry | ConvExpiredEntry)[];
|
|
121
|
+
/** Held links are in the hash chain, but contribute neither state nor messages. */
|
|
122
|
+
held(): number[];
|
|
123
|
+
/** The state at the head; undefined before `create`. */
|
|
124
|
+
state(): ConvSnapshot | undefined;
|
|
125
|
+
/** The state after the entry at `seq`, as a log holding only entries 1..seq has it. */
|
|
126
|
+
at(seq: number): ConvSnapshot | undefined;
|
|
127
|
+
/** What a member knows of the event `id`. */
|
|
128
|
+
message(id: string): ConvMessage | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* Gossip (spec §16.4): `ahead` past the head (fetch up to it first); `consistent` when h(seq) is ours; `diverged`
|
|
131
|
+
* when this log has another hash at that seq, a fork whose evidence is the signed event that named it.
|
|
132
|
+
*/
|
|
133
|
+
relate(head: ConvHead): AnchorRelation;
|
|
134
|
+
/**
|
|
135
|
+
* Checks one entry and appends it, in order: I-JSON and size (`invalid_request`, `too_large`); schema, realm and
|
|
136
|
+
* cid (`invalid_request`); the chain, seq = head + 1 and prev = h(head) (`conv_conflict`); the signature
|
|
137
|
+
* (`bad_signature`); the live checks given (`expired`, `unknown_device`); a reused id (`duplicate`); then the
|
|
138
|
+
* authority rules of spec §16.7. Returns the entry's h(seq).
|
|
139
|
+
*/
|
|
140
|
+
apply(raw: unknown, options?: ConvApplyOptions): Hash;
|
|
141
|
+
/**
|
|
142
|
+
* Appends a `msg` entry whose content the relay expired (spec §16.4, "Retention"), by its digest: the chain stays
|
|
143
|
+
* verifiable and the state does not change. The create (seq 1) never expires (`invalid_request`), nor does any op:
|
|
144
|
+
* a later op head that names this seq fails.
|
|
145
|
+
*/
|
|
146
|
+
applyExpired(raw: ConvExpiredEntry): Hash;
|
|
147
|
+
/**
|
|
148
|
+
* Checks a key share against this log (spec §16.6); a share is not an entry, so nothing changes. In order: I-JSON,
|
|
149
|
+
* size and schema, realm and cid (`invalid_request`, `too_large`); the signature (`bad_signature`); the live checks
|
|
150
|
+
* given (`expired`, `unknown_device`); `by.agent` absent (`forbidden`); `opHead` names an op of this log
|
|
151
|
+
* (`conv_conflict`); then, in the state at `opHead`, the author's person is a member participant person
|
|
152
|
+
* (`not_member`) and the recipient's person a participant person (`forbidden`), and live, the recipient device is a
|
|
153
|
+
* current device of that person (`unknown_device`). Returns the frozen event.
|
|
154
|
+
*/
|
|
155
|
+
checkShare(raw: unknown, live?: ConvLiveChecks): ConvKeysEvent;
|
|
156
|
+
}
|
|
157
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type MachineId } from "../schema/common.js";
|
|
3
|
+
import { type KeyShare } from "../schema/conversation.js";
|
|
4
|
+
/** One ratchet step: the message key for this index and the chain key for the next. */
|
|
5
|
+
export declare function chainStep(ck: Uint8Array): {
|
|
6
|
+
mk: Uint8Array;
|
|
7
|
+
next: Uint8Array;
|
|
8
|
+
};
|
|
9
|
+
/** A sender chain as a device stores it. */
|
|
10
|
+
export declare const SenderChainState: z.ZodObject<{
|
|
11
|
+
epoch: z.ZodNumber;
|
|
12
|
+
device: z.ZodString;
|
|
13
|
+
start: z.ZodNumber;
|
|
14
|
+
origin: z.ZodString;
|
|
15
|
+
idx: z.ZodNumber;
|
|
16
|
+
ck: z.ZodString;
|
|
17
|
+
startedAt: z.ZodNumber;
|
|
18
|
+
}, z.core.$strict>;
|
|
19
|
+
export type SenderChainState = z.infer<typeof SenderChainState>;
|
|
20
|
+
/** The sending side of one chain. `next()` hands out each message key once. */
|
|
21
|
+
export declare class SenderChain {
|
|
22
|
+
#private;
|
|
23
|
+
readonly epoch: number;
|
|
24
|
+
readonly device: MachineId;
|
|
25
|
+
/** The index this chain starts at. */
|
|
26
|
+
readonly start: number;
|
|
27
|
+
readonly startedAt: number;
|
|
28
|
+
private constructor();
|
|
29
|
+
/** A new chain at `idx` (default 0) of `epoch`, from a fresh random chain key. */
|
|
30
|
+
static start(args: {
|
|
31
|
+
epoch: number;
|
|
32
|
+
device: MachineId;
|
|
33
|
+
idx?: number;
|
|
34
|
+
now: number;
|
|
35
|
+
}): SenderChain;
|
|
36
|
+
/** `start` with a caller-chosen chain key: for test vectors, and for a device restoring its own state. */
|
|
37
|
+
static startWith(args: {
|
|
38
|
+
epoch: number;
|
|
39
|
+
device: MachineId;
|
|
40
|
+
idx?: number;
|
|
41
|
+
now: number;
|
|
42
|
+
}, ck: Uint8Array): SenderChain;
|
|
43
|
+
static restore(raw: unknown): SenderChain;
|
|
44
|
+
export(): SenderChainState;
|
|
45
|
+
/** The index the next message gets. */
|
|
46
|
+
get idx(): number;
|
|
47
|
+
/** The share that lets a receiver open every message of this chain: its chain key at its start. */
|
|
48
|
+
share(): KeyShare;
|
|
49
|
+
/** The key for the next message, and its index. The chain moves past it. */
|
|
50
|
+
next(): {
|
|
51
|
+
idx: number;
|
|
52
|
+
mk: Uint8Array;
|
|
53
|
+
};
|
|
54
|
+
/** True once the chain has sent `LIMITS.chainMessages` messages or is `LIMITS.chainMaxMs` old: rotate before the next. */
|
|
55
|
+
due(now: number): boolean;
|
|
56
|
+
/** A fresh chain in the same epoch, starting at this chain's next index. Share it before sending on it. */
|
|
57
|
+
rotate(now: number): SenderChain;
|
|
58
|
+
/** `rotate` with a caller-chosen chain key, for test vectors. */
|
|
59
|
+
rotateWith(now: number, ck: Uint8Array): SenderChain;
|
|
60
|
+
}
|
|
61
|
+
export declare const ReceiverChainsState: z.ZodObject<{
|
|
62
|
+
chains: z.ZodArray<z.ZodObject<{
|
|
63
|
+
epoch: z.ZodNumber;
|
|
64
|
+
device: z.ZodString;
|
|
65
|
+
start: z.ZodNumber;
|
|
66
|
+
origin: z.ZodString;
|
|
67
|
+
pos: z.ZodNumber;
|
|
68
|
+
ck: z.ZodString;
|
|
69
|
+
skipped: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodString], null>>;
|
|
70
|
+
}, z.core.$strict>>;
|
|
71
|
+
}, z.core.$strict>;
|
|
72
|
+
export type ReceiverChainsState = z.infer<typeof ReceiverChainsState>;
|
|
73
|
+
/**
|
|
74
|
+
* The receiving side: every chain this device holds for one conversation, by (epoch, sender device). It derives
|
|
75
|
+
* forward only and hands out each message key once; a message that arrives out of order uses a key kept from an
|
|
76
|
+
* earlier derivation, at most `LIMITS.skippedKeys` per sender and epoch. The chain keys at each share's index are
|
|
77
|
+
* kept, so the device can share them with a late joiner (history `shared`).
|
|
78
|
+
*/
|
|
79
|
+
export declare class ReceiverChains {
|
|
80
|
+
#private;
|
|
81
|
+
static restore(raw: unknown): ReceiverChains;
|
|
82
|
+
export(): ReceiverChainsState;
|
|
83
|
+
/**
|
|
84
|
+
* Adds a share. The first share at a given (epoch, device, idx) wins: a later one with another key changes
|
|
85
|
+
* nothing, and `add` returns false. Returns true when the share was new.
|
|
86
|
+
*/
|
|
87
|
+
add(raw: KeyShare): boolean;
|
|
88
|
+
/** Every share this device holds, from each chain's start: what it shares with a late joiner. */
|
|
89
|
+
shares(): KeyShare[];
|
|
90
|
+
/**
|
|
91
|
+
* The message key for (`epoch`, `device`, `idx`), used once. `not_found` when no share covers it, or its key was
|
|
92
|
+
* already used or dropped; `too_large` when it is more than `LIMITS.skippedKeys` past the chain's position.
|
|
93
|
+
*/
|
|
94
|
+
messageKey(epoch: number, device: MachineId, idx: number): Uint8Array;
|
|
95
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type RequestActivation } from "../schema/contact-request.js";
|
|
2
|
+
import { type ConvLogEntry } from "../schema/conversation.js";
|
|
3
|
+
import { type ConvApplyOptions, type ConvLog } from "./log.js";
|
|
4
|
+
import { type RequestBinding } from "./requests.js";
|
|
5
|
+
export interface RequestReservation {
|
|
6
|
+
proof: RequestActivation;
|
|
7
|
+
completed?: {
|
|
8
|
+
seq: number;
|
|
9
|
+
digest: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface RequestLogState {
|
|
13
|
+
required: boolean;
|
|
14
|
+
active: boolean;
|
|
15
|
+
reservations: Map<string, RequestReservation>;
|
|
16
|
+
}
|
|
17
|
+
/** Rebuilds permanent activation state even when imported messages have expired. */
|
|
18
|
+
export declare function requestLogState(log: ConvLog, binding: RequestBinding): RequestLogState;
|
|
19
|
+
/** Checks before appending. The relay also checks live org admission on create. No mutation on failure. */
|
|
20
|
+
export declare function checkRequestEvent(log: ConvLog, raw: unknown, binding: RequestBinding, body?: unknown): void;
|
|
21
|
+
/** Member admission seam for the cut-over engine; preserves wrapper authorship and verifies source separately. */
|
|
22
|
+
export declare function applyRequestEntry(log: ConvLog, entry: ConvLogEntry, binding: RequestBinding, options?: ConvApplyOptions): string;
|
|
23
|
+
/** Public author for a verified import, never the importing device. The renderer uses this only after admission. */
|
|
24
|
+
export declare function importedRequestAuthor(log: ConvLog, id: string, binding: RequestBinding): string | undefined;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Identity } from "../identity.js";
|
|
2
|
+
import { RequestImport } from "../schema/contact-request.js";
|
|
3
|
+
import { type ConvMsgEvent } from "../schema/conversation.js";
|
|
4
|
+
import type { UnsealedMsg } from "./seal.js";
|
|
5
|
+
export declare function sealRequestImport(identity: Identity, header: UnsealedMsg, body: RequestImport, mk: Uint8Array): ConvMsgEvent;
|
|
6
|
+
export declare function openRequestImport(raw: unknown, mk: Uint8Array): RequestImport;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Identity } from "../identity.js";
|
|
2
|
+
import { ContactAuthorization, PortableRequest, RequestActivation, RequestDecision, RequestDescriptor, RequestOpening } from "../schema/contact-request.js";
|
|
3
|
+
/** Retained, verified identity history must bind every proof's device to its person; absence fails closed. */
|
|
4
|
+
export type RequestBinding = (person: string, device: string) => boolean;
|
|
5
|
+
export declare const requestCommitment: (opening: RequestOpening) => string;
|
|
6
|
+
export declare const requestDigest: (descriptor: RequestDescriptor) => string;
|
|
7
|
+
export declare function verifyRequestDescriptor(raw: unknown, binding: RequestBinding): RequestDescriptor;
|
|
8
|
+
export declare function verifyPortableRequest(raw: unknown, binding: RequestBinding): PortableRequest;
|
|
9
|
+
export declare function createPortableRequest(identity: Identity, args: Omit<RequestDescriptor, "by" | "sig" | "commitment">, text: string): PortableRequest;
|
|
10
|
+
export declare function verifyRequestDecision(raw: unknown, descriptor: RequestDescriptor, binding: RequestBinding): RequestDecision;
|
|
11
|
+
export declare function signRequestDecision(identity: Identity, d: RequestDescriptor, answer: RequestDecision["answer"], at: number): RequestDecision;
|
|
12
|
+
export declare function verifyRequestActivation(raw: unknown, binding: RequestBinding): RequestActivation;
|
|
13
|
+
export declare function signContactAuthorization(identity: Identity, args: Omit<ContactAuthorization, "sig" | "by">): ContactAuthorization;
|
|
14
|
+
export declare function verifyContactAuthorization(raw: unknown, binding: RequestBinding): ContactAuthorization;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Identity } from "../identity.js";
|
|
2
|
+
import { type EncKey, type MachineId, type PersonId } from "../schema/common.js";
|
|
3
|
+
import { type ConvKeysEvent, ConvMeta, type ConvMsgEvent, type ConvOp, type ConvOpEvent, type KeyShare, MsgBody, SettingBody } from "../schema/conversation.js";
|
|
4
|
+
/** A message event before its body: the header its author signs, with `class: "msg"`. */
|
|
5
|
+
export type UnsealedMsg = Omit<ConvMsgEvent, "body" | "sig">;
|
|
6
|
+
/** A keys event before its body. */
|
|
7
|
+
export type UnsealedKeys = Omit<ConvKeysEvent, "body" | "sig">;
|
|
8
|
+
/** A recipient device of a key share: its person, its machine and its X25519 key, as the realm verified them. */
|
|
9
|
+
export interface ShareRecipient {
|
|
10
|
+
person: PersonId;
|
|
11
|
+
device: MachineId;
|
|
12
|
+
enc: EncKey;
|
|
13
|
+
}
|
|
14
|
+
/** Fixed randomness of one sealed share, for test vectors: the ephemeral X25519 private key and the nonce. */
|
|
15
|
+
export interface ShareRandomness {
|
|
16
|
+
ephemeral: Uint8Array;
|
|
17
|
+
nonce: Uint8Array;
|
|
18
|
+
}
|
|
19
|
+
/** Seals `body` under `mk` as the body of `unsealed` and signs the event. Throws only MessagingError. */
|
|
20
|
+
export declare function sealConvMsg(identity: Identity, unsealed: UnsealedMsg, body: MsgBody, mk: Uint8Array): ConvMsgEvent;
|
|
21
|
+
/** `sealConvMsg` with a caller-chosen nonce: for test vectors only (`@messaging/core/testing`). */
|
|
22
|
+
export declare function sealConvMsgWith(identity: Identity, unsealed: UnsealedMsg, body: MsgBody, mk: Uint8Array, nonce: Uint8Array): ConvMsgEvent;
|
|
23
|
+
/**
|
|
24
|
+
* Opens a `msg` event's body with its message key: `bad_signature` when it fails to open, `too_large` over
|
|
25
|
+
* `LIMITS.convPlaintextBytes`, `invalid_request` when the plaintext is not a `MsgBody` (parsed with `parseWire`).
|
|
26
|
+
* The caller has verified the event's signature.
|
|
27
|
+
*/
|
|
28
|
+
export declare function openConvMsg(event: ConvMsgEvent, mk: Uint8Array): MsgBody;
|
|
29
|
+
/** `create.sealedMeta`: JCS(meta) sealed under the creator's epoch-0 key 0, with the create event's header as AAD. */
|
|
30
|
+
export declare function sealConvMeta(header: Omit<ConvOpEvent, "body" | "sig">, meta: ConvMeta, mk0: Uint8Array): string;
|
|
31
|
+
/** `sealConvMeta` with a caller-chosen nonce: for test vectors only. */
|
|
32
|
+
export declare function sealConvMetaWith(header: Omit<ConvOpEvent, "body" | "sig">, meta: ConvMeta, mk0: Uint8Array, nonce: Uint8Array): string;
|
|
33
|
+
/** Opens a `create` event's `sealedMeta`. Errors as for `openConvMsg`; `not_found` when it has none. */
|
|
34
|
+
export declare function openConvMeta(event: ConvOpEvent, mk0: Uint8Array): ConvMeta;
|
|
35
|
+
/**
|
|
36
|
+
* Seals `shares` to one recipient device as the body of `unsealed`, and signs the event: one `keys` event per
|
|
37
|
+
* recipient device (spec §16.6), at most `LIMITS.keyShares` chain keys. Throws only MessagingError.
|
|
38
|
+
*/
|
|
39
|
+
export declare function sealKeyShare(identity: Identity, unsealed: UnsealedKeys, recipient: ShareRecipient, shares: KeyShare[]): ConvKeysEvent;
|
|
40
|
+
/** `sealKeyShare` with fixed randomness: for test vectors only. */
|
|
41
|
+
export declare function sealKeyShareWith(identity: Identity, unsealed: UnsealedKeys, recipient: ShareRecipient, shares: KeyShare[], randomness: ShareRandomness): ConvKeysEvent;
|
|
42
|
+
/**
|
|
43
|
+
* Opens a `keys` event sealed to `identity`'s device: `not_found` when it is another device's, `bad_signature` when it
|
|
44
|
+
* fails to open, `invalid_request` when the plaintext is not a share payload. The caller has checked the event
|
|
45
|
+
* (`ConvLog.checkShare`).
|
|
46
|
+
*/
|
|
47
|
+
export declare function openKeyShares(event: ConvKeysEvent, identity: Identity): KeyShare[];
|
|
48
|
+
/** A sealed op as its author builds it: the op without `sealed`. */
|
|
49
|
+
export type UnsealedStateOp = Omit<Extract<ConvOp, {
|
|
50
|
+
op: "meta";
|
|
51
|
+
}>, "sealed"> | Omit<Extract<ConvOp, {
|
|
52
|
+
op: "setting";
|
|
53
|
+
}>, "sealed">;
|
|
54
|
+
/** An op event before its body is sealed: the header and the op without `sealed`. */
|
|
55
|
+
export type UnsealedOpEvent = Omit<ConvOpEvent, "body" | "sig"> & {
|
|
56
|
+
body: UnsealedStateOp;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Seals `content` (a `ConvMeta` for `meta`, a `SettingBody` of the op's kind for `setting`) under `mk`, the author's
|
|
60
|
+
* message key at the op's (`epoch`, `idx`), and signs the op event. Throws only MessagingError.
|
|
61
|
+
*/
|
|
62
|
+
export declare function sealConvState(identity: Identity, unsealed: UnsealedOpEvent, content: unknown, mk: Uint8Array): ConvOpEvent;
|
|
63
|
+
/** `sealConvState` with a caller-chosen nonce: for test vectors only. */
|
|
64
|
+
export declare function sealConvStateWith(identity: Identity, unsealed: UnsealedOpEvent, content: unknown, mk: Uint8Array, nonce: Uint8Array): ConvOpEvent;
|
|
65
|
+
/**
|
|
66
|
+
* Opens a `meta` or `setting` op with the author's message key at its (`epoch`, `idx`): a `ConvMeta`, or a
|
|
67
|
+
* `SettingBody` whose `s` is the op's `kind` (`invalid_request` otherwise). `not_found` for another op; errors as
|
|
68
|
+
* for `openConvMsg`. The caller has applied the event to the log.
|
|
69
|
+
*/
|
|
70
|
+
export declare function openConvState(event: ConvOpEvent, mk: Uint8Array): ConvMeta | SettingBody;
|