1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
//! Data rooms — storage, wire types, and authorization.
//!
//! A **data room** is a shared space whose access is governed by credentials the *room
//! itself* issues, not by anything this service stores. That single property is what the
//! rest of this module is arranged around, and it is worth stating before the types,
//! because it inverts the assumption every other keyspace here is built on.
//!
//! # What this module deliberately does not hold
//!
//! **There is no member list.** Not omitted for now — there must not be one. Authorization
//! is a presentation carrying a membership credential and an authority chain, verified
//! against the room's own identifier. The moment this service keeps a roster and consults
//! it, three things stop being true at once: the room can no longer move to another host
//! without reissuing credentials, this service has become part of the room's membership
//! definition, and a room whose contents we cannot read acquires a member list we can.
//!
//! So the row below carries an owner, a visibility, an epoch and a retention period, and
//! nothing about who belongs. See `docs/05-design-notes/data-rooms.md` §1 (invariant I5).
//!
//! # What this service can and cannot see
//!
//! Set by the room's [`Visibility`], fixed at creation:
//!
//! | | `Open` | `Attributed` | `Private` |
//! |---|---|---|---|
//! | Record content | cleartext | sealed | sealed |
//! | Which member acted | visible | visible | unlinkable proof |
//! | Owner | visible | visible | visible |
//!
//! The owner is visible at every tier on purpose. A room whose contents nobody here can
//! read still has a party answerable for it existing — for quota, for abuse, and for the
//! lifecycle notice in §9 of the design note.
//!
//! # Scope of this module
//!
//! # Why this is a crate and not part of a service
//!
//! A room's storage and its authorization need nothing from a community service. That is
//! not an accident of layering — it is invariant I5 restated as a dependency graph: because
//! a room is authorized by credentials the room itself issued, the code deciding a room
//! operation cannot need a roster, a policy engine, or a session store. So it does not have
//! one, and the compiler enforces that this crate depends on `vti-common` and nothing else.
//!
//! The concrete win is a second consumer. A **room host** — someone hosting their own rooms
//! on their own infrastructure, topology T1 of the design — stores ciphertext and verifies
//! presentations. Without this crate, doing that means shipping an entire community service:
//! member lifecycle, policy, credential issuance, a website, an admin SPA. With it, a room
//! host is this crate plus a dispatch surface.
//!
//! Three layers:
//!
//! - [`storage`] — the keyspaces and their invariants.
//! - [`wire`] — the Trust-Task payload types, hand-written against the schemas in
//! `trustoverip/dtgwg-trust-tasks-tf#346` until the generated bindings publish.
//! - [`authz`] — deciding whether an operation is allowed, **without reading any host's ACL
//! or roster**. The invariant the whole design rests on.
//!
//! The Trust-Task **handlers** are deliberately *not* here. Dispatch is a service's spine,
//! and a spine is not extractable — see `docs/05-design-notes/vta-service-decomposition.md`.
//! Each host writes its own thin handlers over these three layers.
/// The room's group-key layer (RFC 9420), behind the `mls` feature.
///
/// Off by default: a host that only stores ciphertext needs none of it, and OpenMLS is a
/// substantial dependency to make it carry.
/// The epoch key chain that keeps a room readable across a membership change.
use ;
/// Keyspace holding one row per room.
///
/// Named here rather than in a host's registry because the *name* is part of the storage
/// contract: two hosts using different names could not serve the same room's data directory.
pub const ROOMS_KEYSPACE: &str = "rooms";
/// Keyspace holding room records.
pub const ROOM_RECORDS_KEYSPACE: &str = "room_records";
/// Keyspace holding the epoch key chain — one wrapped key per epoch advance.
///
/// Held by the host as opaque ciphertext it cannot read. See [`wire::EpochLink`].
pub const ROOM_EPOCH_LINKS_KEYSPACE: &str = "room_epoch_links";
/// Whether a room keeps its history readable across a membership change.
///
/// **Immutable for the life of a room**, like [`Visibility`] and for the same reason: the
/// links either exist for an epoch or they do not, and a policy change cannot manufacture
/// key material that was never sealed or unseal what was already severed.
///
/// The choice is a real one and it is not obvious, so it is stated at creation rather than
/// defaulted silently. See [`wire::EpochLink`] for what each side costs.
/// How much of a room this service can see.
///
/// **Immutable for the life of a room.** A downgrade cannot un-see cleartext, and an
/// upgrade would protect only what came after while presenting as though it protected
/// everything. To change the visibility of some material, make another room and move it
/// deliberately.
/// A room, as this service holds it.
///
/// Note what is absent: no members, no keys, no credentials. This service is told the
/// epoch *number* so it can serve the right ciphertext, and never the key.
/// Curation state of a record.
/// One record.
///
/// On `Attributed` and `Private` rooms `sealed` carries the ciphertext and `cleartext` is
/// `None`; on `Open` it is the other way round. Enforced at the operations layer rather
/// than the type, because the invariant is per-room and the type is per-record.
/// Unix seconds as an RFC 3339 timestamp.
///
/// A value beyond what a timestamp can express renders as the epoch rather than panicking:
/// a listing is a read path, and a corrupt stored time should not take the room down.