@ccmsg/protocol 0.5.1 → 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.1",
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
  );