Subject: [calsify] JSCalendar format for individual recurrences

From: "Bron Gondwana" <[email-masked]>
Date: Wed, 01 May 2019 12:09:10 -0400

iMIP (RFC6047) messages can contain iTIP (RFC5546) files which refer to just a single recurrence of a full event.

JSCalendar doesn't have an easy way to specify this kind of "orphan" event, I'm considering that maybe it looks like this:

BASE EVENT:

 {
 "duration" : "PT1H",
 "recurrenceRule" : {
 "frequency" : "monthly"
 },
 "start" : "2016-01-01T13:00:00",
 "timeZone" : "Europe/Vienna",
 "title" : "foo",
 "uid" : "89eee195-600b-423b-b3a6-52b3a420e556",
 [...]
 }

OVERRIDE:

 {
 "recurrenceOverrides": {
 "2016-03-01T13:00:00": {
 "duration" : "PT2H",
 "start" : "2016-03-01T13:00:00",
 "timeZone" : "Europe/Vienna",
 "title" : "longfoo",
 [...]
 },
 },
 "uid" : "89eee195-600b-423b-b3a6-52b3a420e556",
 }

COMBINED EVENT:

 {
 "duration" : "PT1H",
 "recurrenceOverrides": {
 "2016-03-01T13:00:00": {
 "duration" : "PT2H",
 "title" : "longfoo",
 },
 },
 "recurrenceRule" : {
 "frequency" : "monthly"
 },
 "start" : "2016-01-01T13:00:00",
 "timeZone" : "Europe/Vienna",
 "title" : "foo",
 "uid" : "89eee195-600b-423b-b3a6-52b3a420e556",
 [...]
 }

WHERE [...] is all the other properties. They have to be duplicated inside the recurrenceOverride when it's orphaned, because we don't have the parent event to refer to.

I THINK this format is sufficient for representing a single recurrence, but let me know if you think I'm missing something. Should this have a separate @type key to tell you that it's an orphan or is the presence of recurrenceOverrides without a recurrenceRule sufficient?

Bron.

--
 Bron Gondwana, CEO, FastMail Pty Ltd
 [email-masked]

---

From: "Neil Jenkins" <[email-masked]>
Date: Wed, 01 May 2019 21:30:13 -0400

No, this isn't quite right. The following properties are non-optional at the top-level:

 * @type
 * uid
 * updated
 * start

Further, the following cannot be overridden in recurrenceOverrides:

 * @type
 * uid
 * relatedTo
 * prodId
 * method
 * isAllDay
 * recurrenceRule
 * recurrenceOverrides
 * replyTo

So then I think we have two options for representing orphaned single occurrences for iTIP. The first option is to define a new property (maybe `occurrenceId` or `recurrenceId`) which denotes this is an occurrence, and then represent it like a non-recurring event; this is similar to iCalendar. The value of this property would be the key used in `recurrenceOverrides` in the original event, i.e. the same as the start time of the occurrence unless this is overridden.

The other option is a bit more complicated:
 1. Remove any `recurrenceRule` property.
 2. Remove any `recurrenceOverride` entries for other occurrences.
 3. Move/merge any top-level property that may be overridden into the recurrence override.
 4. Either change the top-level `start` to match the recurrence override id, or add an entry to `recurrenceOverrides` to delete the original start.

The advantage of option (2) is that you can represent being invited to multiple individual occurrences of the master event in a single object; indeed, this is probably the format I would choose for storing the event in if my backend was storing JSCalendar. However for iTIP purposes *I think option (1) is much simpler and probably the better choice*.

Example of option 2; original event:

{
  "@type": "jsevent",
  "uid": "7722a0b8-1b39-431c-88df-b03f1096b81f",
  "updated": "2019-05-01T00:00:00Z",
  "start": "2019-05-06T09:00:00",
  "duration": "PT1H0M0S",
  "timeZone": "Australia/Melbourne",
  "title": "The Meeting",
  "description": "The Notes.",
  "locations": {
    "1": {
      "name": "The Location"
    }
  },
  "isAllDay": false,
  "recurrenceRule": {
    "frequency": "weekly"
  },
  "recurrenceOverrides": {
    "2019-05-13T09:00:00": {
      "participants/c3BlY2lhbEBleGFtcGxlLmNvbQ": {
        "name": "",
        "email": "[email-masked]",
        "sendTo": {
          "imip": "mailto:[email-masked]"
        },
        "kind": "individual",
        "roles": {
          "attendee": true
        },
        "participationStatus": "needs-action",
        "expectReply": true
      }
    }
  },
  "status": "confirmed",
  "replyTo": {
    "imip": "mailto:[email-masked]"
  },
  "participants": {
    "dXNlckBnbWFpbC5jb20": {
      "name": "My Name",
      "email": "[email-masked]",
      "sendTo": {
        "imip": "mailto:[email-masked]"
      },
      "kind": "individual",
      "roles": {
        "attendee": true,
        "owner": true
      },
      "participationStatus": "accepted",
      "expectReply": false
    },
    "amFuZUBleGFtcGxlLmNvbQ": {
      "name": "",
      "email": "[email-masked]",
      "sendTo": {
        "imip": "mailto:[email-masked]"
      },
      "kind": "individual",
      "roles": {
        "attendee": true
      },
      "participationStatus": "needs-action",
      "expectReply": true
    }
  }
}

and representation of single override, for [email-masked]:

{
  "@type": "jsevent",
  "uid": "7722a0b8-1b39-431c-88df-b03f1096b81f",
  "updated": "2019-05-01T00:00:00Z",
  "start": "2019-05-06T09:00:00",
  "isAllDay": false,
  "recurrenceOverrides": {
    "2019-05-13T09:00:00": {
      "duration": "PT1H0M0S",
      "timeZone": "Australia/Melbourne",
      "title": "The Meeting",
      "description": "The Notes.",
      "locations": {
        "1": {
          "name": "The Location"
        }
      },
      "status": "confirmed",
      "participants": {
        "dXNlckBnbWFpbC5jb20": {
          "name": "My Name",
          "email": "[email-masked]",
          "sendTo": {
            "imip": "mailto:[email-masked]"
          },
          "kind": "individual",
          "roles": {
            "attendee": true,
            "owner": true
          },
          "participationStatus": "accepted",
          "expectReply": false
        },
        "amFuZUBleGFtcGxlLmNvbQ": {
          "name": "",
          "email": "[email-masked]",
          "sendTo": {
            "imip": "mailto:[email-masked]"
          },
          "kind": "individual",
          "roles": {
            "attendee": true
          },
          "participationStatus": "needs-action",
          "expectReply": true
        },
        c3BlY2lhbEBleGFtcGxlLmNvbQ": {
          "name": "",
          "email": "[email-masked]",
          "sendTo": {
            "imip": "mailto:[email-masked]"
          },
          "kind": "individual",
          "roles": {
            "attendee": true
          },
          "participationStatus": "needs-action",
          "expectReply": true
        }
      }
    }
  },
  "replyTo": {
    "imip": "mailto:[email-masked]"
  }
}

Neil.

---

From: Marten Gajda <[email-masked]>
Date: Thu, 2 May 2019 15:08:23 +0200

Why would you move/merge the non-overridden properties from the top
level event into the override? That doesn't seem to make much sense,
especially considering the fact that there may be more than one override
you're invited to.

For consistency I strongly prefer option #2.

I think your example lacks the result of the 4th step, i.e. it would be
interpreted as two instances.

Marten

> So then I think we have two options for representing orphaned single
> occurrences for iTIP. The first option is to define a new property
> (maybe |occurrenceId| or |recurrenceId|) which denotes this is an
> occurrence, and then represent it like a non-recurring event; this is
> similar to iCalendar. The value of this property would be the key used
> in |recurrenceOverrides| in the original event, i..e. the same as the
> start time of the occurrence unless this is overridden.
>
>  1. Remove any |recurrenceRule| property.
>  2. Remove any |recurrenceOverride| entries for other occurrences.
>  4. Either change the top-level |start| to match the recurrence
>     override id, or add an entry to |recurrenceOverrides| to delete
>     the original start.
>
>
> The advantage of option (2) is that you can represent being invited to
> multiple individual occurrences of the master event in a single
> object; indeed, this is probably the format I would choose for storing
> the event in if my backend was storing JSCalendar. However for iTIP
> purposes *I think option (1) is much simpler and probably the better
> choice*.
>
>         "email": "special@example..com <mailto:[email-masked]>",
>         "sendTo": {
>           "imip": "mailto:[email-masked] <mailto:[email-masked]>"
>         },
>         "kind": "individual",
>         "roles": {
>           "attendee": true
>         },
>     "imip": "mailto:[email-masked] <mailto:[email-masked]>"
>   },
>   "participants": {
>     "dXNlckBnbWFpbC5jb20": {
>       "name": "My Name",
>       "email": "[email-masked] <mailto:[email-masked]>",
>       "sendTo": {
>         "imip": "mailto:[email-masked] <mailto:[email-masked]>"
>       },
>       "kind": "individual",
>       "roles": {
>         "attendee": true,
>         "owner": true
>       },
>       "email": "[email-masked] <mailto:[email-masked]>",
>       "sendTo": {
>         "imip": "mailto:[email-masked] <mailto:[email-masked]>"
>       },
>       "kind": "individual",
>       "roles": {
>         "attendee": true
>       },
> and representation of single override, for [email-masked]
> <mailto:[email-masked]>:
>
> {
>   "@type": "jsevent",
>           "email": "[email-masked] <mailto:[email-masked]>",
>           "sendTo": {
>             "imip": "mailto:me@example..com <mailto:[email-masked]>"
>           },
>           "kind": "individual",
>           "roles": {
>             "attendee": true,
>             "owner": true
>           },
>           "email": "[email-masked] <mailto:[email-masked]>",
>           "sendTo": {
>             "imip": "mailto:[email-masked] <mailto:[email-masked]>"
>           },
>           "kind": "individual",
>           "roles": {
>             "attendee": true
>           },
>           "email": "[email-masked] <mailto:[email-masked]>",
>           "sendTo": {
>             "imip": "mailto:[email-masked] <mailto:[email-masked]>"
>           },
>           "kind": "individual",
>           "roles": {
>             "attendee": true
>           },
>     "imip": "mailto:[email-masked] <mailto:[email-masked]>"
>   }
> }
>
> Neil.
>
> _______________________________________________
> calsify mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/calsify

-- 
Marten Gajda
CEO

dmfs GmbH
Schandauer Straße 34
01309 Dresden
GERMANY

phone: +49 177 4427167
email: [email-masked]

Managing Director: Marten Gajda
Registered address: Dresden
Registered No.: AG Dresden HRB 34881
VAT Reg. No.: DE303248743

---

From: Cyrus Daboo <[email-masked]>
Date: Thu, 02 May 2019 10:20:50 -0400

Hi Neil,

--On May 1, 2019 at 9:30:13 PM -0400 Neil Jenkins <[email-masked]> 
wrote:

> occurrences for iTIP. The first option is to define a new property (maybe
> `occurrenceId` or `recurrenceId`) which denotes this is an occurrence,
> and then represent it like a non-recurring event; this is similar to
> iCalendar. The value of this property would be the key used in
> `recurrenceOverrides` in the original event, i.e. the same as the start
> time of the occurrence unless this is overridden.

Please note that iTIP (as defined by 5546) overrides the "required" 
properties of base iCalendar (see all the tables in that spec which 
indicate new requirements for properties in iTIP messages). There are some 
good reasons for that (e.g., for a CANCEL message you really don't need to 
send much more than enough information to uniquely identify the instance 
being cancelled - e.g. no need for "start").

Of course the question arises as to whether you want to use iTIP as the 
basis for JSCalendar scheduling messages, or whether you want to do 
something different. There are well known issues with parts of iTIP that we 
attempted to correct with the last update, but I don't think we really 
managed to do that. I think it makes sense for this group to re-think 
scheduling in its entirety to see if a cleaner, simpler solution is viable, 
fitting in with the overall approach for JSCalendar and jcal etc. I am not 
sure whether that belongs as part of the JMAP re-charter or something for 
this WG.

In any case, there is a need to store individual recurrences in calendar 
stores once they have been delivered by iTIP and yes, those must conform to 
the property requirements of JSCalendar since they must be valid JSCalendar 
objects.

-- 
Cyrus Daboo

---

From: "Neil Jenkins" <[email-masked]>
Date: Thu, 02 May 2019 20:42:15 -0400

On Thu, 2 May 2019, at 23:08, Marten Gajda wrote:
> Why would you move/merge the non-overridden properties from the top level event into the override? That doesn't seem to make much sense, especially considering the fact that there may be more than one override you're invited to. 

It's so you don't leak data that the organiser might expect to be hidden from the user. e.g. If your regular event had a note "Top secret code is XXY, don't tell Bob" and then you invite Bob to one specific instance of the event and override the note to be "Regular meeting, nothing to see here".

Maybe it's being over-paranoid but the "right" thing to do I think is to only expose the property values for the instance the user is invited to. There are several different ways you could do this of course.

> For consistency I strongly prefer option #2.

I'm not convinced. I think for iTIP messages #option 1 is easier to handle. Another issue with #2: suppose you are invited to two different occurrences of a general recurring event. Does the iTIP version include the data for both of the recurrences in a single object? The iCalendar version will be two separate VEVENTs in two separate iTIP emails, so this is awkward. But if you represent each one separately, how do you know the second email is an invitation to a second instance, rather than updating the data you received in the first email?

Stepping back, there are two separate reasons for needing this representation:
 1. You receive an iCalendar iMIP message and want to translate it into JSCalendar for a client; option #1 is a closer mapping to the iCalendar here and seems much easier for the client to manage. The iCalendar doesn't have sufficient data to do option #2.
 2. You want to send scheduling messages to another system. I don't think we need to be in any rush with this, since iMIP messages will be needed for a long time to come for backwards compatibility, and as Cyrus points out I think it would be better to take our time and reassess whether we can use the opportunity to improve on this process too.

> I think your example lacks the result of the 4th step, i.e. it would be interpreted as two instances.

You're right, sorry; the perils of editing it by hand!

On Fri, 3 May 2019, at 00:20, Cyrus Daboo wrote:
> indicate new requirements for properties in iTIP messages).

Right, I think this would probably be more sensible than requiring a dummy start value.

> sure whether that belongs as part of the JMAP re-charter or something for 

This might be worthwhile. What I would like to see in the future is something like this:

 * The initial invitation for an event is still sent out as an email. For backwards compatibility there will need to be a text/calendar part for iMIP-understanding clients. But then after that we would add a JSCalendar part that newer clients would use instead; this would just be ignored by older clients presuming they support multipart/alternative correctly.
 * JSCalendar supports multiple "replyTo" mechanisms, so it could support both iMIP and AdvancedFutureSchedule™️. 
 * If it's just an email client without an integrated calendar it might support sending a basic RSVP via iMIP. But if it has a real calendar attached, it adds the event there and the RSVP is sent back via AdvancedFutureSchedule™️, which also sets up some non-mail communication channel (probably a secret URL) that allows updates to be sent for *that event only*. So the organising calendar server can now keep the invitee's copy of the event up-to-date when other people change RSVP etc. without cluttering up the user's inbox and without the fragility of going via email. It's up to the user's calendar to decide how/whether to notify the user if the event changes, so the user gets to decide what's important. (Maybe some users really do want to be emailed every time someone else's RSVP status changes!)

The nice thing about this is it ”solves“ the identity problem that has plagued us for years, by having the initial invitation still go via email; it's up to the user how to route this into their calendar. Once there though, it can upgrade all future scheduling negotiation for that event to another system. Because it's on a per-event basis, it doesn't have the problem of ”stealing“ ownership of a calendar (like for example we have with Google Calendar, where a user moves their email from G Suite to our platform but never receive any invitations sent from Google Calendar because it still thinks it owns their calendar).

> the property requirements of JSCalendar since they must be valid JSCalendar 

Agreed. But the interchange/scheduling format doesn't need to be the same as the storage format.

Neil.