Skip to content

Commit 72e1ea7

Browse files
committed
Add journaling
This PR introduces the journaling concept as discussed in: #219
1 parent 588ebfb commit 72e1ea7

3 files changed

Lines changed: 289 additions & 5 deletions

File tree

IETF-RFC.md

Lines changed: 158 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,147 @@ knowing if the Sending Party understood and processed the reshare
12361236
request or not. In all cases, the Receiving Server MUST NOT reshare
12371237
a Resource without an explicit grant from the Sending Server.
12381238

1239+
# Journaling
1240+
1241+
OCM messages can be lost during outages or network failures.
1242+
Journaling provides a mechanism for Receiving Servers to detect missing
1243+
messages and recover state by replaying missed messages from the
1244+
Sending Server's journal.
1245+
1246+
A Sending Server that supports journaling maintains a sequential
1247+
journal of all OCM messages sent to each Receiving Server. Journal
1248+
entries are scoped per (sender, receiver, message type) tuple, where
1249+
message type is one of `share`, `notification`, or `invite-accepted`.
1250+
1251+
Following [RFC6648], the `OCM-Journal-Id` header is used without the
1252+
`X-` prefix convention for application-specific parameters.
1253+
1254+
## OCM-Journal-Id Header
1255+
1256+
A Sending Server that exposes the `journaling` capability MUST include
1257+
an `OCM-Journal-Id` header in all outgoing OCM messages (Share
1258+
Creation Notifications, Share Acceptance Notifications, and Invitation
1259+
Acceptance requests). The value is a positive integer representing the
1260+
monotonically increasing sequence number for this message within the
1261+
(sender, receiver, message type) tuple.
1262+
1263+
A Receiving Server MUST NOT reject a message solely because it lacks
1264+
an `OCM-Journal-Id` header. When a Receiving Server observes an
1265+
`OCM-Journal-Id` header for the first time from a given Sending Server,
1266+
and the value is not 1, the Receiving Server SHOULD request a full
1267+
journal replay to synchronize state.
1268+
1269+
## Journal Scoping
1270+
1271+
Each journal is scoped per (sender, receiver, message type) tuple.
1272+
This means a Sending Server maintains separate sequences for shares,
1273+
notifications, and invitation acceptances sent to each Receiving Server.
1274+
1275+
Both Sending and Receiving Servers need to track journal IDs:
1276+
1277+
* The Sending Server tracks outgoing journal IDs to assign sequential
1278+
numbers and serve replay requests.
1279+
* The Receiving Server tracks incoming journal IDs per Sending Server
1280+
to detect gaps and trigger replays when needed.
1281+
1282+
## Populating Outgoing IDs
1283+
1284+
A Sending Server that implements journaling SHOULD:
1285+
1286+
1. Maintain a sequential ID for each outgoing message, per
1287+
(receiver, message type) pair.
1288+
2. For each Receiving Server, find all outgoing messages of a given
1289+
type and order by timestamp.
1290+
3. Assign a sequential ID to each entry, scoped to the individual
1291+
journal for that (receiver, message type) pair, for outgoing
1292+
messages only.
1293+
4. When sending the next message, include the next sequential ID in
1294+
the `OCM-Journal-Id` header.
1295+
1296+
## Populating Incoming IDs
1297+
1298+
A Receiving Server MUST NOT require the presence of a sequential ID
1299+
in an incoming message. However, when a Receiving Server first
1300+
observes an `OCM-Journal-Id` header from a Sending Server and the
1301+
value is not 1, the Receiving Server SHOULD request all messages of
1302+
that type from journal ID 1 up to the observed ID.
1303+
1304+
The Receiving Server SHOULD verify that all locally stored shares or
1305+
invitations from that Sending Server can be correlated with an
1306+
incoming journal ID. If the Receiving Server finds an orphan object
1307+
in its local database (one that cannot be tagged with a corresponding
1308+
incoming journal ID), it SHOULD be removed, as the Sending Server
1309+
does not consider it a valid entry.
1310+
1311+
## Compaction
1312+
1313+
A Sending Server MAY compact its journal by replacing entries that
1314+
have been effectively cancelled with `noop` entries. This preserves
1315+
sequence continuity while reflecting the current effective state.
1316+
1317+
For example, given the following journal:
1318+
1319+
1. Share resource α with person X
1320+
2. Share resource β with person Y
1321+
3. Unshare resource α with person X
1322+
4. Share resource γ with person X
1323+
1324+
After compaction, the journal replay would return:
1325+
1326+
1. NOOP
1327+
2. Share resource β with person Y
1328+
3. NOOP
1329+
4. Share resource γ with person X
1330+
1331+
Entries 1 and 3 are replaced with noops because the share of resource
1332+
α was effectively cancelled by the subsequent unshare.
1333+
1334+
## Noop Message
1335+
1336+
A `noop` is a minimal OCM message type represented as an empty JSON
1337+
object `{}`. It is used exclusively in journal replay responses to
1338+
represent compacted entries. The `noop` message type preserves sequence
1339+
continuity: Receiving Servers can verify that no journal IDs are missing
1340+
while understanding that the compacted entries have no effect.
1341+
1342+
## Journal Replay
1343+
1344+
To replay missed messages, the Receiving Server SHOULD make an HTTP
1345+
GET request
1346+
1347+
* to the `/journal` path in the Sending Server's OCM API
1348+
* with the `since` query parameter set to the last known journal ID
1349+
(use 0 to request the full compacted journal)
1350+
* with the `messageType` query parameter set to the type of journal
1351+
to query (`share`, `notification`, or `invite-accepted`)
1352+
* using TLS
1353+
* using httpsig [RFC9421]
1354+
1355+
HTTP Request Signatures [RFC9421] are REQUIRED for journal replay.
1356+
Servers that do not support the `http-sig` capability MUST NOT expose
1357+
the journal replay endpoint.
1358+
1359+
The Sending Server identifies the caller via the HTTP signature and
1360+
serves the appropriate journal entries for the (sender, caller,
1361+
messageType) tuple.
1362+
1363+
### Response
1364+
1365+
The response is a JSON object containing an `entries` array. Each
1366+
entry is a JSON object with the following fields:
1367+
1368+
* REQUIRED journalId (integer)
1369+
The sequence number for this entry.
1370+
* REQUIRED message (object)
1371+
The OCM message body. The message type is determined by the
1372+
`messageType` query parameter. For `share` journals this is a Share
1373+
Creation Notification object; for `notification` journals a Share
1374+
Acceptance Notification object; for `invite-accepted` journals an
1375+
Invitation Acceptance object. Compacted entries are represented as
1376+
an empty object `{}` (noop).
1377+
1378+
Entries MUST be ordered by journalId in ascending order.
1379+
12391380
# IANA Considerations
12401381

12411382
## Well-Known URI for the Discovery
@@ -1352,6 +1493,14 @@ signed using HTTP Signatures. Bearer tokens MUST be treated as
13521493
confidential and never logged, persisted beyond their lifetime, or
13531494
transmitted over unsecured channels.
13541495

1496+
## Journaling
1497+
1498+
The journal replay endpoint MUST only be accessible via HTTP Request
1499+
Signatures [RFC9421]. Servers that do not support the `http-sig`
1500+
capability MUST NOT expose the journal replay endpoint. Journal
1501+
responses may contain sensitive information about shared resources
1502+
and MUST be served only to authenticated and authorized callers.
1503+
13551504
# References
13561505

13571506
## Normative References
@@ -1364,12 +1513,17 @@ March 1997.
13641513
"[Uniform Resource Identifier (URI): Generic Syntax
13651514
](https://datatracker.ietf.org/doc/html/rfc3986)", January 2005
13661515

1367-
[RFC4918] Dusseault, L. M. "[HTTP Extensions for Web Distributed
1368-
Authoring and Versioning](https://datatracker.ietf.org/html/rfc4918/)",
1516+
[RFC4918] Dusseault, L. M. "[HTTP Extensions for Web
1517+
Distributed Authoring and Versioning](
1518+
https://datatracker.ietf.org/doc/html/rfc4918/)",
13691519
June 2007.
13701520

1521+
[RFC6648] Saint-Andre, P., Crocker, D. and Overell, M.,
1522+
"[Deprecating the "X-" Prefix and Similar Constructs in Application
1523+
Protocols](https://datatracker.ietf.org/doc/html/rfc6648)", June 2012.
1524+
13711525
[RFC6749] Hardt, D. (ed), "[The OAuth 2.0 Authorization Framework](
1372-
https://datatracker.ietf.org/html/rfc6749)", October 2012.
1526+
https://datatracker.ietf.org/doc/html/rfc6749)", October 2012.
13731527

13741528
[RFC7515] Jones, M., Bradley, J., Sakimura, N., "[JSON Web Signature
13751529
(JWS)](https://datatracker.ietf.org/doc/html/rfc7515)", May 2015.
@@ -1382,7 +1536,7 @@ Signature Algorithm (EdDSA)](
13821536
https://datatracker.ietf.org/doc/html/rfc8032)", January 2017.
13831537

13841538
[RFC8174] Leiba, B. "[Ambiguity of Uppercase vs Lowercase in RFC 2119
1385-
Key Words](https://datatracker.ietf.org/html/rfc8174)", May 2017.
1539+
Key Words](https://datatracker.ietf.org/doc/html/rfc8174)", May 2017.
13861540

13871541
[RFC8615] Nottingham, M. "[Well-Known Uniform Resource Identifiers
13881542
(URIs)](https://datatracker.ietf.org/doc/html/rfc8615)", May 2019

schemas/ocm-discovery.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
},
2323
"capabilities": {
2424
"type": "array",
25-
"description": "Capabilities values of 'exchange-token', 'webdav-uri', 'protocol-object', 'invites', 'invite-wayf' defined in draft",
25+
"description": "Capabilities values of 'exchange-token', 'webdav-uri', 'protocol-object', 'invites', 'invite-wayf', 'journaling' defined in draft",
2626
"items": {
2727
"type": "string"
2828
}

spec.yaml

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ paths:
6060
This endpoint is used by a Sending Server to notify a Receiving Server that
6161
a new Share has been created. See [Share Creation Notification](https://github.com/cs3org/OCM-API/blob/develop/IETF-RFC.md#share-creation-notification)
6262
for more details.
63+
parameters:
64+
- $ref: "#/components/parameters/journalId"
6365
requestBody:
6466
content:
6567
application/json:
@@ -142,6 +144,8 @@ paths:
142144
that concerns a previously known entity, such as a Share or a trusted User.
143145
See [Share Acceptance Notification](https://github.com/cs3org/OCM-API/blob/develop/IETF-RFC.md#share-acceptance-notification)
144146
for more details.
147+
parameters:
148+
- $ref: "#/components/parameters/journalId"
145149
requestBody:
146150
content:
147151
application/json:
@@ -208,6 +212,8 @@ paths:
208212
This optional endpoint is used to inform the Sender that an Invitation was accepted.
209213
See [Invite flow](https://github.com/cs3org/OCM-API/blob/develop/IETF-RFC.md#invite-flow)
210214
for more details.
215+
parameters:
216+
- $ref: "#/components/parameters/journalId"
211217
requestBody:
212218
content:
213219
application/json:
@@ -247,6 +253,84 @@ paths:
247253
application/json:
248254
schema:
249255
$ref: "#/components/schemas/Error"
256+
/journal:
257+
get:
258+
summary: Journal Replay endpoint
259+
description: >
260+
This optional endpoint allows a Receiving Server to request OCM messages
261+
it may have missed from a Sending Server. The Sending Server maintains a
262+
sequential journal of all OCM messages sent to each Receiving Server,
263+
scoped per (sender, receiver, message type) tuple. Journal IDs are
264+
monotonically increasing integers assigned to each outgoing message.
265+
266+
267+
Servers that expose the `journaling` capability MUST support this
268+
endpoint. This endpoint MUST require HTTP Request Signatures [RFC9421]
269+
for authentication. Servers that do not support the `http-sig` capability
270+
MUST NOT expose this endpoint.
271+
272+
273+
The Sending Server identifies the caller via the HTTP signature and
274+
serves the appropriate journal entries for the (sender, caller, messageType)
275+
tuple.
276+
277+
278+
The journal MAY be compacted: when a sequence of messages effectively
279+
cancels out (e.g. a share followed by an unshare of the same resource),
280+
the Sending Server MAY replace them with `noop` entries. This preserves
281+
sequence continuity while reflecting the current effective state.
282+
parameters:
283+
- name: since
284+
in: query
285+
required: true
286+
description: >
287+
Return journal entries with a journalId strictly greater than
288+
this value. Use 0 to request the full (possibly compacted) journal.
289+
schema:
290+
type: integer
291+
minimum: 0
292+
- name: messageType
293+
in: query
294+
required: true
295+
description: >
296+
The type of OCM message journal to query. Each message type
297+
maintains a separate journal per (sender, receiver) pair.
298+
schema:
299+
type: string
300+
enum:
301+
- share
302+
- notification
303+
- invite-accepted
304+
responses:
305+
"200":
306+
description: >
307+
Successfully retrieved journal entries. The entries MUST be ordered
308+
by journalId in ascending order.
309+
content:
310+
application/json:
311+
schema:
312+
type: object
313+
required:
314+
- entries
315+
properties:
316+
entries:
317+
type: array
318+
items:
319+
$ref: "#/components/schemas/JournalEntry"
320+
"403":
321+
description: >
322+
Caller cannot be authenticated via HTTP Request Signatures or
323+
is not authorized to access this journal.
324+
content:
325+
application/json:
326+
schema:
327+
$ref: "#/components/schemas/Error"
328+
"501":
329+
description: This server does not support journaling.
330+
content:
331+
application/json:
332+
schema:
333+
$ref: "#/components/schemas/Error"
250334
components:
251335
parameters:
252336
id:
@@ -256,6 +340,19 @@ components:
256340
required: true
257341
schema:
258342
type: string
343+
journalId:
344+
name: OCM-Journal-Id
345+
in: header
346+
required: false
347+
description: >
348+
Sequential journal identifier for this message, scoped per
349+
(sender, receiver, message type) tuple. Servers that expose the
350+
`journaling` capability MUST include this header in all outgoing
351+
OCM messages. Receiving Servers that do not support journaling
352+
MAY ignore this header.
353+
schema:
354+
type: integer
355+
minimum: 1
259356
page:
260357
name: page
261358
in: query
@@ -405,6 +502,7 @@ components:
405502
- http-sig
406503
- invites
407504
- invite-wayf
505+
- journaling
408506
- notifications
409507
- protocol-object
410508
- webdav-uri
@@ -901,3 +999,35 @@ components:
901999
type: number
9021000
description: Number of seconds before this access_token will need to be refreshed.
9031001
example: 300
1002+
JournalEntry:
1003+
type: object
1004+
required:
1005+
- journalId
1006+
- message
1007+
properties:
1008+
journalId:
1009+
type: integer
1010+
minimum: 1
1011+
description: >
1012+
Monotonically increasing sequence number scoped per
1013+
(sender, receiver, message type) tuple. Journal IDs MUST be
1014+
strictly increasing but need not be contiguous.
1015+
example: 42
1016+
message:
1017+
description: >
1018+
The OCM message body. The message type is determined by the
1019+
`messageType` query parameter used to request the journal.
1020+
Compacted entries are represented as Noop (empty object `{}`).
1021+
oneOf:
1022+
- $ref: "#/components/schemas/NewShare"
1023+
- $ref: "#/components/schemas/NewNotification"
1024+
- $ref: "#/components/schemas/AcceptedInvite"
1025+
- $ref: "#/components/schemas/Noop"
1026+
Noop:
1027+
type: object
1028+
description: >
1029+
A no-operation message representing a compacted journal entry.
1030+
When a sequence of messages effectively cancels out (e.g. a share
1031+
followed by an unshare of the same resource), the Sending Server
1032+
MAY replace them with noop entries to preserve journal sequence
1033+
continuity.

0 commit comments

Comments
 (0)