Re: [Jmap] new JMAP server for prototyping

Neil Jenkins <> Wed, 19 May 2021 03:06 UTC

Return-Path: <>
Received: from localhost (localhost []) by (Postfix) with ESMTP id 5E6663A1AF5 for <>; Tue, 18 May 2021 20:06:01 -0700 (PDT)
X-Virus-Scanned: amavisd-new at
X-Spam-Flag: NO
X-Spam-Score: -2.097
X-Spam-Status: No, score=-2.097 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, HTML_MESSAGE=0.001, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_PASS=-0.001, URIBL_BLOCKED=0.001] autolearn=ham autolearn_force=no
Authentication-Results: (amavisd-new); dkim=pass (2048-bit key) header.b=h8wKQCne; dkim=pass (2048-bit key) header.b=NElayC55
Received: from ([]) by localhost ( []) (amavisd-new, port 10024) with ESMTP id W6-EZAztRwfB for <>; Tue, 18 May 2021 20:05:55 -0700 (PDT)
Received: from ( []) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by (Postfix) with ESMTPS id AAFD33A1AF2 for <>; Tue, 18 May 2021 20:05:55 -0700 (PDT)
Received: from compute4.internal (compute4.nyi.internal []) by mailout.nyi.internal (Postfix) with ESMTP id EF59A5C01A4 for <>; Tue, 18 May 2021 23:05:53 -0400 (EDT)
Received: from imap43 ([]) by compute4.internal (MEProxy); Tue, 18 May 2021 23:05:53 -0400
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=; h=mime-version:message-id:in-reply-to :references:date:from:to:subject:content-type; s=fm2; bh=Xuvjwal 1ZTyRbPwaJvKMvFnZ5ZdVIdcgXZ6EhKleYN0=; b=h8wKQCneilOivNHD1Z6dAKE yujzIb9VFCYs0Vjp1W5Yu+4fsK0gXsjJpt3Hap9bYcNrqB1VY0fcTi4ZwbwexYoa FkZtJs+t8SVWP4jCWpsiI/blOgA6N8ESkBfAFnh8lpOtqOW+MXy4zxbS3Ll8+e/q TqBpWpg/TYLV9SKnda3xFp/eb5hpIz63ivte9mrW00DzCDeaME6RPQWHKkSIU1Ac 6uHOAhdM+sEFLRGVQkmbYMTRkVGUiVC5KmoV34lSqlN5+7Ykdvg1tObfnLm57dGe 4CDw5wD0A6w++GfPbEkzLOorP3KXNDta6RWf+GmMU2FkK9o6mowwVrKJcmLWlHg= =
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=; h=content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to:x-me-proxy :x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; bh=Xuvjwa l1ZTyRbPwaJvKMvFnZ5ZdVIdcgXZ6EhKleYN0=; b=NElayC559K/r78NpYkvtX9 XFgtQYFqLyAfiR7rirr3oGdERp9HT3DXi4CArc7vdb+hKnxcLVPmn1LeNmBG+ZkS lpMUnPAd3X8z+2q919MUC4VKR2kFvPu7nRhDivEKqHMFwA3M8VPQ97TXpPWucPmz 0M8eILh+uQkrRrB885nAQ+2FbpkzHH7q5U+f/O5+P+OytSLAqZ4HVYbrl52uCae6 inTVgvCSxIrhJIsTYYk90OjZJ9jYKl9reVdcNWhJYVlcONsJU31ETkZggsy1jOvn be8mk2sIZvMgWnChJndTmNFsSkkAdd/VzcNwq6q6fWEm14bQBC+yeyNQnYvSQHHA ==
X-ME-Sender: <xms:kYCkYES1IJBy9EjuoewZe4PF2s_e7Ohr-gg--3v5xj7ea8xpDvi-7A> <xme:kYCkYBxibhuaLXq3inL_uka3NYmtiMeNF3TcvvcebQLQjcC3YDSu-QDsQMLJhtFYi vf5r3e48hP4zw>
X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeduledrvdeikedgieefucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne cujfgurhepofgfggfkjghffffhvffutgesrgdtreerreertdenucfhrhhomhepfdfpvghi lhculfgvnhhkihhnshdfuceonhgvihhljhesfhgrshhtmhgrihhlthgvrghmrdgtohhmqe enucggtffrrghtthgvrhhnpeevvdetvdduleekhfeghfetfeettdelhfehfeevffevleek uddtudffieevjeevhfenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepmhgrih hlfhhrohhmpehnvghilhhjsehfrghsthhmrghilhhtvggrmhdrtghomh
X-ME-Proxy: <xmx:kYCkYB1GVYtZLyPn133KHVb_uhS-dfemzjgnMtGFljEgQXbTnIXi4g> <xmx:kYCkYIBU7P1UC_ghUr8ZLCulCpDns-c1WxiXRoCg3L-j8fyQvECswA> <xmx:kYCkYNi-zpO04x3LFY73MBfMvq4eosTcYuNLVmgW8YLuvEjlr8eQJw> <xmx:kYCkYIsE8d-V8R8Hfjy79auH7YQcaeeYD4YOuieosYUWDXa-BDfvMQ>
Received: by mailuser.nyi.internal (Postfix, from userid 501) id A82F918005D; Tue, 18 May 2021 23:05:53 -0400 (EDT)
X-Mailer: Webmail Interface
User-Agent: Cyrus-JMAP/3.5.0-alpha0-701-g78bd539edf-fm-ubox-20210517.001-g78bd539e
Mime-Version: 1.0
Message-Id: <>
In-Reply-To: <>
References: <>
Date: Wed, 19 May 2021 13:05:48 +1000
From: "Neil Jenkins" <>
To: "IETF JMAP Mailing List" <>
Content-Type: multipart/alternative; boundary=f90b4ab456a0490e87b9354105de3841
Archived-At: <>
Subject: Re: [Jmap] new JMAP server for prototyping
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: JSON Message Access Protocol <>
List-Unsubscribe: <>, <>
List-Archive: <>
List-Post: <>
List-Help: <>
List-Subscribe: <>, <>
X-List-Received-Date: Wed, 19 May 2021 03:06:02 -0000

Hi Jamey,

> Hey all: for the past few weeks I've been working on a new JMAP server with the goal of making it easy to prototype new data models.

That's awesome! Nice work.

> I've been confused by a few things in RFC8620. I was only going to give a couple examples but ended up with a complete list of everything I remember having trouble with, so I hope it works out to just dump it here. I have some more notes afterward on my current implementation and plans.
> - Should creating multiple objects allow circular references, or must it be a DAG of new objects? The former seems hard to get right if any of the creations fail.

The relevant bit of the spec here is:

   The final state MUST be valid after the "Foo/set" is finished;
   however, the server may have to transition through invalid
   intermediate states (not exposed to the client) while processing the
   individual create/update/destroy requests.  For example, suppose
   there is a "name" property that must be unique.  A single method call
   could rename an object A => B and simultaneously rename another
   object B => A.  If the final state is valid, this is allowed.
   Otherwise, each creation, modification, or destruction of an object
   should be processed sequentially and accepted/rejected based on the
   current server state.

I don't think we actually considered a data type that supported circular references, but the way the current spec is written I would say that if the circular reference is valid for that data type and the final state is valid, then yes you would be allowed to do that in a single /set. But I agree that's a bit ambiguous, and I think you could probably forbid it in the /set description for the particular data type. Do you have an example in mind?

> - Can result references appear in nested objects within a method's arguments, or only on the arguments directly within the top-level Invocation?

Only on the top-level. The *arguments object* is the top-level object passed in the *Invocation*. So this:

   When processing a method call, the server MUST first check
   the arguments object for any names beginning with "#".

means just the top-level object.

> - Does the client need to ensure that it never uses a creation ID of "foo" if there's some random string whose value happens to be "#foo", or does /set need to know which properties have Id type?

When processing a /set you need to know which properties have Id type (well you need to know all the types really in order to be able to validate the data).

> - If the client uses the same creation ID in two method calls, despite the "SHOULD" cautioning against it, and the second create fails, should the first one's ID continue to be used? Is "the most recently created item with that id" the most recent attempt, or the most recent success?

Most recent success was the intention, but looking at it again I can see how you could interpret it both ways.

> - I think I get it now, but I've been confused about how the responses from /copy relate to its three phases. There's one /copy response covering phases 1 and 2, then optionally a single /set response for phase 3, right?

That's right.

> - What value should the position attribute in a /query response have if the requested position is past the end of the results?

The definition of "position" in the response is:

      The zero-based index of the first result in the "ids" array within
      the complete list of query results.

So I agree it's undefined if there are no results (but I also think it's unimportant, because there are no results to position!). I would just return the requested position I think. It would probably be worth making this well-defined in a future revision.

> - If a client has a sparse array of query results and gets a /queryChanges response indicating that it should delete an object that it didn't have in the array, then I don't see how it can tell whether to shift down any of the objects it does have: the deleted object could have been anywhere. This interface only seems safe so long as clients either always keep a complete prefix of a query's results, or invalidate their cache if told to delete an object they didn't know about.

Close. In this situation you have to invalidate any results in your sparse query after the first gap, but you can still keep the ones before. So if I have:

[A, B, -, -, D, -, -]

and queryChanges tells me that E has been removed, I would have to invalidate after the first gap, so my cached query would now be:

[A, B, -, -. -. -. -]

> - What does property immutability have to do with query result changes? Isn't the only thing that matters whether the property actually changed, rather than whether it could change? Is it just that some reasonable implementations can't compute certain changes for mutable properties?

Yes, it's that the spec was written with the idea that /queryChanges would be implemented based on knowing which records have changed from last time, not based on having the old query cached and comparing it with the new one. (This approach is hard to scale effectively.) If you only know that a record has changed, not which property on that record then you don't know what position it had in the client's current query cache (unless you have cached the old query, as you are doing), so have to tell the client to remove it an readd it to ensure it's correct.

If you can return precise changes with your implementation, that's great! This is really about guidance for what to do when you can calculate usable, if slightly less concise, changes efficiently. The text could definitely be better here, it's written a bit too much with an implementation approach in mind (in general we tried to not do that, as there are multiple good ways of implementing various bits of JMAP).

> - The specification for upToId seems to imply to look up the position of the given ID in the new results, though that isn't entirely clear to me. 

Yes, in the new results.

> But I think it makes more sense to find its position in the old results, and send changes that update the client's cache to the same length. That means the optimization still works even if the selected object is no longer in the results, and puts an upper bound on how much data the client might have to deal with: twice the number of items it has cached already.

I'm not quite sure what you mean by "to the same length", but yes if you have the old query cached you could optimise the case where the upToId is not in the new results a bit further. But again, the spec is written to allow implementers to calculate query changes *without* caching the old query state.

> But overall I've found the spec pretty clear and solid so far.

Great! This was really good feedback. I don't think any of it requires an errata (although if someone on the list disagrees, please pipe up!), but if we publish an updated RFC at some point in the future we know which points to clarify.

> Currently all my tests are property-based randomized testing. I'd like to think my tests might be interesting for other server implementors. If there are test suites I might be able to reuse, I'd love to hear about them.

Getting a good test suite for the JMAP spec is something we would love to have (and know from past experience is important for interoperability). I don't think anyone has published an open reusable one yet.

> I've yet to tackle the session state, blobs, or push, but those don't seem like they'll have hard questions about semantics, just some possibly tricky engineering choices. We'll see how that goes.

I think queryChanges is the trickiest bit to get your head around semantically so hopefully now that's done the rest is not too hard, but do keep us informed of your progress and any other spec issues you find!