@ccmsg/protocol 0.5.0 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ccmsg/protocol",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Wire contract (schema + types + op attribute table) shared by the ccmsg daemon and web UI",
5
5
  "license": "MIT",
6
6
  "author": "kawaz",
@@ -1,4 +1,5 @@
1
1
  import { type Static, Type } from "@sinclair/typebox";
2
+ import { InstanceInfo } from "../common/hello.ts";
2
3
  import { topicFrame } from "../envelope.ts";
3
4
  import { InstanceId, Sid, Timestamp } from "../identifiers.ts";
4
5
  import { SessionMetaFields } from "../session-meta.ts";
@@ -189,5 +190,15 @@ export const PeersFrame = topicFrame(
189
190
  Type.Object({
190
191
  peers: Type.Array(PeerInfo),
191
192
  last_live: Type.Array(LastLiveSession),
193
+ /** The instances the sending one can see, itself included, as `hello`
194
+ * answers it. Carried here so that a link going down is something a
195
+ * subscriber learns from the topic it is already on, rather than by
196
+ * greeting again to find out. It is the sender's own view like the two
197
+ * lists above — `reachable` says whether that instance can reach the one it
198
+ * names, which two instances may legitimately disagree about.
199
+ *
200
+ * Absent from an instance that states no view; a client then keeps what it
201
+ * last knew rather than reading the omission as nothing being reachable. */
202
+ instances: Type.Optional(Type.Array(InstanceInfo)),
192
203
  }),
193
204
  );
package/src/envelope.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type Static, type TSchema, Type } from "@sinclair/typebox";
2
2
  import { ErrorBody } from "./errors.ts";
3
- import { InstanceId } from "./identifiers.ts";
3
+ import { InstanceId, Role, Sid } from "./identifiers.ts";
4
4
 
5
5
  /** The generation of this wire contract. Within a generation, only optional
6
6
  * fields and whole new ops may be added; a removal or a change of meaning
@@ -8,14 +8,27 @@ import { InstanceId } from "./identifiers.ts";
8
8
  * connections and on mesh links alike. */
9
9
  export const PROTOCOL_VERSION = 2;
10
10
 
11
+ /** The identity a forwarded request is dispatched as: the connection the
12
+ * forwarding instance received it on, in the two fields that decide anything —
13
+ * the role the attribute table is read against, and the session it speaks for. */
14
+ export const CallerIdentity = Type.Object(
15
+ {
16
+ role: Role,
17
+ /** Present exactly when the role is `session`, as in `hello`. */
18
+ sid: Type.Optional(Sid),
19
+ },
20
+ { $id: "CallerIdentity" },
21
+ );
22
+ export type CallerIdentity = Static<typeof CallerIdentity>;
23
+
11
24
  /** Fields a request carries in addition to its own arguments.
12
25
  *
13
26
  * `request_id` pairs a reply with its request so one connection can run its
14
27
  * requests concurrently instead of in arrival order. Uniqueness only has to
15
28
  * hold among one connection's in-flight requests.
16
29
  *
17
- * The three mesh fields are the whole of the mesh plane: an op forwarded to
18
- * another instance is the same op in the same shape, wrapped in these. */
30
+ * The mesh fields are the whole of the mesh plane: an op forwarded to another
31
+ * instance is the same op in the same shape, wrapped in these. */
19
32
  export const RequestEnvelope = Type.Object(
20
33
  {
21
34
  request_id: Type.String({ minLength: 1 }),
@@ -28,6 +41,19 @@ export const RequestEnvelope = Type.Object(
28
41
  /** Instances this request has already passed through, in order. A request
29
42
  * that would revisit an instance is dropped rather than looped. */
30
43
  hops: Type.Optional(Type.Array(InstanceId)),
44
+ /** Who the forwarding instance says made this request.
45
+ *
46
+ * The destination runs every stage of dispatch again against this identity
47
+ * — the role check, the capability check, the whole of it — rather than
48
+ * taking the forwarder's outcome for it. What it does take is the identity
49
+ * itself: the forwarder is an authenticated peer, so what it says about who
50
+ * called is believed, which is the assumption that holds inside one
51
+ * deployment and nowhere else.
52
+ *
53
+ * A forwarded request that names none is dispatched as the `instance` role
54
+ * it arrived on, which the attribute table already answers: an
55
+ * instance-local op called by an instance is `forbidden`. */
56
+ caller: Type.Optional(CallerIdentity),
31
57
  },
32
58
  { $id: "RequestEnvelope" },
33
59
  );
@@ -23,9 +23,14 @@ export const DIRECT_DELIVERY_TAG = "cross-session-message";
23
23
  *
24
24
  * Not the sender's session and not a `uds:` path. Those are the addresses the
25
25
  * harness itself dials, and dialing one that has gone ends the recipient's turn
26
- * in failure; the sender here is a daemon that offers no such socket anyway.
27
- * A name asks for no answer, so there is nothing to dangle the way back is
28
- * the reply line, which the recipient runs rather than the harness. */
26
+ * in failure. A name asks for no answer, so there is nothing to dangle — the
27
+ * way back is the reply line, which the recipient runs rather than the harness.
28
+ *
29
+ * The frame this wrapper is written in is a separate address: there the
30
+ * instance may name a `uds:` socket of its own, which is where the receiving
31
+ * harness writes what became of the delivery. Those receipts are negative only
32
+ * — `refused`, `denied`, `dropped`, `expired`, `held` — so silence within the
33
+ * window is what says the message was taken. */
29
34
  export const DIRECT_DELIVERY_FROM = "ccmsg";
30
35
 
31
36
  /** What the recipient is told the sender is doing. `prompting` is what a peer