Skip to content

Commit ed7cba8

Browse files
committed
Add journaling
This PR introduces the journaling concept as discussed in: #219
1 parent a1311af commit ed7cba8

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
@@ -1232,6 +1232,147 @@ knowing if the Sending Party understood and processed the reshare
12321232
request or not. In all cases, the Receiving Server MUST NOT reshare
12331233
a Resource without an explicit grant from the Sending Server.
12341234

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

12371378
## Well-Known URI for the Discovery
@@ -1348,6 +1489,14 @@ signed using HTTP Signatures. Bearer tokens MUST be treated as
13481489
confidential and never logged, persisted beyond their lifetime, or
13491490
transmitted over unsecured channels.
13501491

1492+
## Journaling
1493+
1494+
The journal replay endpoint MUST only be accessible via HTTP Request
1495+
using signatures [RFC9421]. Servers that do not support the `http-sig`
1496+
capability MUST NOT expose the journal replay endpoint. Journal
1497+
responses may contain sensitive information about shared resources
1498+
and MUST be served only to authenticated and authorized callers.
1499+
13511500
# Copying conditions
13521501

13531502
The author(s) agree to grant third parties the irrevocable right to
@@ -1368,12 +1517,17 @@ March 1997.
13681517
"[Uniform Resource Identifier (URI): Generic Syntax
13691518
](https://datatracker.ietf.org/doc/html/rfc3986)", January 2005
13701519

1371-
[RFC4918] Dusseault, L. M. "[HTTP Extensions for Web Distributed
1372-
Authoring and Versioning](https://datatracker.ietf.org/html/rfc4918/)",
1520+
[RFC4918] Dusseault, L. M. "[HTTP Extensions for Web
1521+
Distributed Authoring and Versioning](
1522+
https://datatracker.ietf.org/doc/html/rfc4918/)",
13731523
June 2007.
13741524

1525+
[RFC6648] Saint-Andre, P., Crocker, D. and Overell, M.,
1526+
"[Deprecating the "X-" Prefix and Similar Constructs in Application
1527+
Protocols](https://datatracker.ietf.org/doc/html/rfc6648)", June 2012.
1528+
13751529
[RFC6749] Hardt, D. (ed), "[The OAuth 2.0 Authorization Framework](
1376-
https://datatracker.ietf.org/html/rfc6749)", October 2012.
1530+
https://datatracker.ietf.org/doc/html/rfc6749)", October 2012.
13771531

13781532
[RFC7515] Jones, M., Bradley, J., Sakimura, N., "[JSON Web Signature
13791533
(JWS)](https://datatracker.ietf.org/doc/html/rfc7515)", May 2015.
@@ -1386,7 +1540,7 @@ Signature Algorithm (EdDSA)](
13861540
https://datatracker.ietf.org/doc/html/rfc8032)", January 2017.
13871541

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

13911545
[RFC8615] Nottingham, M. "[Well-Known Uniform Resource Identifiers
13921546
(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
@@ -43,6 +43,8 @@ paths:
4343
This endpoint is used by a Sending Server to notify a Receiving Server that
4444
a new Share has been created. See [Share Creation Notification](https://github.com/cs3org/OCM-API/blob/develop/IETF-RFC.md#share-creation-notification)
4545
for more details.
46+
parameters:
47+
- $ref: "#/components/parameters/journalId"
4648
requestBody:
4749
content:
4850
application/json:
@@ -125,6 +127,8 @@ paths:
125127
that concerns a previously known entity, such as a Share or a trusted User.
126128
See [Share Acceptance Notification](https://github.com/cs3org/OCM-API/blob/develop/IETF-RFC.md#share-acceptance-notification)
127129
for more details.
130+
parameters:
131+
- $ref: "#/components/parameters/journalId"
128132
requestBody:
129133
content:
130134
application/json:
@@ -191,6 +195,8 @@ paths:
191195
This optional endpoint is used to inform the Sender that an Invitation was accepted.
192196
See [Invite flow](https://github.com/cs3org/OCM-API/blob/develop/IETF-RFC.md#invite-flow)
193197
for more details.
198+
parameters:
199+
- $ref: "#/components/parameters/journalId"
194200
requestBody:
195201
content:
196202
application/json:
@@ -230,6 +236,84 @@ paths:
230236
application/json:
231237
schema:
232238
$ref: "#/components/schemas/Error"
239+
/journal:
240+
get:
241+
summary: Journal Replay endpoint
242+
description: >
243+
This optional endpoint allows a Receiving Server to request OCM messages
244+
it may have missed from a Sending Server. The Sending Server maintains a
245+
sequential journal of all OCM messages sent to each Receiving Server,
246+
scoped per (sender, receiver, message type) tuple. Journal IDs are
247+
monotonically increasing integers assigned to each outgoing message.
248+
249+
250+
Servers that expose the `journaling` capability MUST support this
251+
endpoint. This endpoint MUST require HTTP Request Signatures [RFC9421]
252+
for authentication. Servers that do not support the `http-sig` capability
253+
MUST NOT expose this endpoint.
254+
255+
256+
The Sending Server identifies the caller via the HTTP signature and
257+
serves the appropriate journal entries for the (sender, caller, messageType)
258+
tuple.
259+
260+
261+
The journal MAY be compacted: when a sequence of messages effectively
262+
cancels out (e.g. a share followed by an unshare of the same resource),
263+
the Sending Server MAY replace them with `noop` entries. This preserves
264+
sequence continuity while reflecting the current effective state.
265+
parameters:
266+
- name: since
267+
in: query
268+
required: true
269+
description: >
270+
Return journal entries with a journalId strictly greater than
271+
this value. Use 0 to request the full (possibly compacted) journal.
272+
schema:
273+
type: integer
274+
minimum: 0
275+
- name: messageType
276+
in: query
277+
required: true
278+
description: >
279+
The type of OCM message journal to query. Each message type
280+
maintains a separate journal per (sender, receiver) pair.
281+
schema:
282+
type: string
283+
enum:
284+
- share
285+
- notification
286+
- invite-accepted
287+
responses:
288+
"200":
289+
description: >
290+
Successfully retrieved journal entries. The entries MUST be ordered
291+
by journalId in ascending order.
292+
content:
293+
application/json:
294+
schema:
295+
type: object
296+
required:
297+
- entries
298+
properties:
299+
entries:
300+
type: array
301+
items:
302+
$ref: "#/components/schemas/JournalEntry"
303+
"403":
304+
description: >
305+
Caller cannot be authenticated via HTTP Request Signatures or
306+
is not authorized to access this journal.
307+
content:
308+
application/json:
309+
schema:
310+
$ref: "#/components/schemas/Error"
311+
"501":
312+
description: This server does not support journaling.
313+
content:
314+
application/json:
315+
schema:
316+
$ref: "#/components/schemas/Error"
233317
components:
234318
parameters:
235319
id:
@@ -239,6 +323,19 @@ components:
239323
required: true
240324
schema:
241325
type: string
326+
journalId:
327+
name: OCM-Journal-Id
328+
in: header
329+
required: false
330+
description: >
331+
Sequential journal identifier for this message, scoped per
332+
(sender, receiver, message type) tuple. Servers that expose the
333+
`journaling` capability MUST include this header in all outgoing
334+
OCM messages. Receiving Servers that do not support journaling
335+
MAY ignore this header.
336+
schema:
337+
type: integer
338+
minimum: 1
242339
page:
243340
name: page
244341
in: query
@@ -388,6 +485,7 @@ components:
388485
- http-sig
389486
- invites
390487
- invite-wayf
488+
- journaling
391489
- notifications
392490
- protocol-object
393491
- webdav-uri
@@ -884,3 +982,35 @@ components:
884982
type: number
885983
description: Number of seconds before this access_token will need to be refreshed.
886984
example: 300
985+
JournalEntry:
986+
type: object
987+
required:
988+
- journalId
989+
- message
990+
properties:
991+
journalId:
992+
type: integer
993+
minimum: 1
994+
description: >
995+
Monotonically increasing sequence number scoped per
996+
(sender, receiver, message type) tuple. Journal IDs MUST be
997+
strictly increasing but need not be contiguous.
998+
example: 42
999+
message:
1000+
description: >
1001+
The OCM message body. The message type is determined by the
1002+
`messageType` query parameter used to request the journal.
1003+
Compacted entries are represented as Noop (empty object `{}`).
1004+
oneOf:
1005+
- $ref: "#/components/schemas/NewShare"
1006+
- $ref: "#/components/schemas/NewNotification"
1007+
- $ref: "#/components/schemas/AcceptedInvite"
1008+
- $ref: "#/components/schemas/Noop"
1009+
Noop:
1010+
type: object
1011+
description: >
1012+
A no-operation message representing a compacted journal entry.
1013+
When a sequence of messages effectively cancels out (e.g. a share
1014+
followed by an unshare of the same resource), the Sending Server
1015+
MAY replace them with noop entries to preserve journal sequence
1016+
continuity.

0 commit comments

Comments
 (0)