diff --git a/docs/cloverage/codecov.json b/docs/cloverage/codecov.json
index 8a65628..def2f8e 100644
--- a/docs/cloverage/codecov.json
+++ b/docs/cloverage/codecov.json
@@ -1,18 +1,55 @@
 {"coverage":
- {"dog_and_duck/quack/quack.clj":
+ {"dog_and_duck/quack/picky/constants.clj":
+  [null, 1, null, null, null, null, null, null, null, null, null, null,
+   null, null, null, null, null, null, null, null, 1, null, null, null,
+   null, 1, null, null, null, null, 1, null, null, null, null, null, 1,
+   null, null, null, 1, null, 1, null, null, null, null, null, null,
+   null, null, null, null, null, 1, null, 1, null, 1, 1, 1, 1, 1, 1,
+   null, 1, null, null, null, null, 1, null, null, null, null, 1, null,
+   null, null, null],
+  "dog_and_duck/quack/picky/utils.clj":
+  [null, 1, null, null, null, null, null, null, null, null, null, null,
+   null, null, null, null, null, null, null, null, null, null, null,
+   null, null, null, null, null, null, null, null, null, null, null, 1,
+   null, null, 7, null, 1, null, null, null, 0, null, 1, null, null,
+   null, null, null, 0, 0, 0, 0, 0, null, 1, null, null, 0, 0, 0, null,
+   null, null, null, true, null, null, 0, null, null, 1, null, null,
+   null, 4, null, 1, null, null, null, 0, 0, 0, 0, null, 1, null, null,
+   null, 3, 3, true, 3, null, 1, null, null, null, true, true, 45, 45,
+   45, 70, 45, null, 0, 0, null, 0, 0, null, 1, null, null, null, null,
+   null, null, true, 162, 66, true, 6, null, null, null, 53, null,
+   null, 3, null, 1, null, null, null, null, null, null, null, null,
+   220, 220, 220, 220, null, 220, null, 220, null, 220, 220, true,
+   null, 0, 0, null, 53, null, null, 2, 2, null, 1, null, null, null,
+   true, null, 1, null, null, null, null, null, null, 0, 0, 0, 0, 0, 0,
+   0, 0, 0, null, 0, 0, 0, 0, 0, 0, null, 1, null, null, null, null,
+   null, null, null, null, null, 0, 0, null, 0, null, 3, null, null,
+   null, 3, null, 1, null, null, null, null, null, 0, null, 0, 0, null,
+   null, 1, null, null, null, null, null, null, true, 53, 53, 53, 3,
+   53, 53, 41, 53, 9, 53, 16, null, 0, 0, 0, 0, 0, null, null, 1, null,
+   null, null, null, null, null, null, null, null, null, null, null,
+   null, null, null, null, null, 0, 0, 0, 0, 0, 0, null, 0, 0, 0, null,
+   null, 0, 0, 0, null, 0, 0, 0, 0, 0, null, 0, 0, 0, 0, 0, null, 1,
+   null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, null, 0, 0, 0, 0],
+  "dog_and_duck/quack/quack.clj":
   [null, 1, null, null, null, null, null, null, null, null, null, null,
    null, null, null, null, null, null, null, null, null, null, null,
    null, null, null, null, null, null, null, null, null, null, null,
-   null, null, null, null, 1, null, null, null, null, null, null, null,
+   null, null, null, null, null, null, 1, null, null, null, null, null,
+   null, null, null, null, null, null, null, null, null, null, null,
+   null, null, null, 34, null, 34, null, 1, null, null, null, null,
+   null, 4, null, 4, null, 1, null, 3, null, 3, null, 1, null, null,
+   null, null, null, null, null, 0, 0, 0, 0, null, null, null, null, 1,
+   null, 0, null, 0, null, 1, null, 0, null, 0, null, 1, null, null,
+   null, null, null, 0, 0, 0, null, null, 1, null, null, null, null,
+   null, true, true, true, true, 2, 1, null, 1, 1, true, true, 1, null,
+   null, null, null, null, null, null, null, 0, 0, 0, 0, null, null, 1,
+   null, null, 0, null, 1, null, null, 0, null, 1, null, null, 0, null,
+   1, null, null, 2, null, null],
+  "dog_and_duck/quack/picky/fault_messages.clj":
+  [null, 1, null, null, null, null, null, null, null, null, null, null,
+   null, null, null, null, null, null, null, null, null, 1, null, 1,
    null, null, null, null, null, null, null, null, null, null, null,
-   null, 35, null, 35, null, 1, null, null, null, null, null, 4, null,
-   4, null, 1, null, 3, null, 3, null, 1, null, null, null, null, null,
-   null, null, 0, 0, 0, 0, null, null, null, null, 1, null, 0, null, 0,
-   null, 1, null, 0, null, 0, null, 1, null, null, null, null, null, 0,
-   0, 0, null, null, 1, null, null, null, null, null, true, true, true,
-   true, 2, 1, null, 1, 1, true, true, 1, null, null, null, null, null,
-   null, null, null, 0, 0, 0, 0, null, null, 1, null, null, 0, null, 1,
-   null, null, 0, null, 1, null, null, 0, null, 1, null, null, 2, null,
    null],
   "dog_and_duck/utils/process.clj":
   [null, 1, null, null, null, null, null, null, null, null, null, null,
@@ -30,8 +67,15 @@
   "dog_and_duck/scratch/parser.clj":
   [null, 1, null, null, null, null, null, null, null, null, null, null,
    null, null, null, null, null, null, null, null, null, null, null,
-   null, 1, null, null, null, 11, 11, 11, 11, 11, true, 0, null, 1,
-   null, 1, 0, 0, 0, 0, 0, 0, 1, null, 1],
+   null, 1, null, null, null, 9, 9, 9, 9, 9, true, 0, null, null, null,
+   null, null, null, null, null, null, null, null, null, null],
+  "dog_and_duck/quack/picky/required_properties.clj":[null, 1],
+  "dog_and_duck/quack/picky/control_variables.clj":
+  [null, 1, null, null, null, null, null, null, null, null, null, null,
+   null, null, null, null, null, null, null, null, 1, null, null, null,
+   null, null, null, null, null, null, null, null, null, null, null,
+   null, null, null, null, null, null, 1, null, null, null, null, null,
+   null, null, null],
   "clj_activitypub/core.clj":
   [null, 1, null, null, null, null, null, null, null, null, null, 1,
    null, null, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, null, 1, null,
@@ -53,44 +97,19 @@
    null, null, null, null, null, null, null, null, null, null, null,
    null, null, null, null, null, null, null, null, null, null, null,
    null, null, null, null, null, null, null, null, null, null, null,
-   null, 1, null, null, null, null, null, null, null, null, null, null,
-   null, null, null, null, null, null, null, null, null, null, 1, null,
-   null, null, null, null, null, null, null, null, 1, null, null, null,
-   1, null, 1, null, null, null, null, null, null, null, null, null,
-   null, null, 1, null, 1, null, 1, 1, 1, 1, 1, 1, null, 1, null, null,
-   0, null, 1, null, null, null, null, null, 0, 0, 0, 0, 0, null, 1,
-   null, null, null, true, true, 46, 46, 46, 72, 46, null, 0, 0, null,
-   0, 0, null, 1, null, null, null, null, 1, null, null, null, null, 1,
-   null, null, null, null, null, null, true, 261, 123, true, 6, null,
-   null, null, 89, null, null, 3, null, 1, null, null, null, null,
-   null, null, null, null, 372, 372, 372, 372, null, 372, null, 372,
-   null, 372, 372, true, null, 0, 0, null, true, null, null, 6, 6,
-   null, 1, null, null, null, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0,
-   0, null, 0, 0, 0, 0, 0, 0, null, 1, null, null, null, null, null,
-   true, 89, 89, 89, 5, 89, 89, 66, 89, 13, 89, 23, null, 0, 0, 0, 0,
-   0, null, null, 0, null, 1, null, null, null, null, 0, null, 16, 16,
-   null, 0, null, 0, null, 12, null, 1, null, null, true, 17, 17, 17,
-   17, 17, 14, 13, 8, null, 1, null, 0, 3, null, 1, null, null, null,
-   null, 1, null, null, null, null, null, 1, null, null, 12, null, 1,
-   null, null, null, 8, 8, true, 8, null, 1, null, null, true, 3, 3, 3,
-   3, 1, 3, 3, 3, 3, null, 1, null, null, null, null, 1, null, null,
-   null, null, 1, null, null, null, 4, null, 1, null, null, null, 0, 0,
-   0, 0, null, 1, null, null, null, null, null, 0, null, 0, 0, null, 1,
-   null, null, null, null, 0, 0, 0, 0, null, null, null, 1, null, null,
-   null, null, null, null, null, null, null, null, null, null, null,
-   null, null, null, 0, 0, 0, 0, 0, 0, null, 0, 0, 0, null, null, 0, 0,
-   null, 0, 0, 0, 0, 0, null, 0, 0, 0, 0, 0, null, 1, null, null, 0,
-   null, 1, null, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null, 0,
-   0, 0, 0, null, 1, null, null, null, true, 0, true, true, null, 1,
-   null, null, null, 1, null, 1, null, null, 1, null, 1, 0, null, null,
-   null, 1, null, null, 1, 1, 1, 1, null, 1, 1, 1, 1, 1, 1, null, 1, 1,
-   1, 0, null, null, null, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-   1, null, 1, null, null, null, null, null, null, null, 0, 0, 0, null,
-   0, 0, 0, 0, null, 1, null, 0, 0, 0, 0, 0, 0, 0, 0, 0],
-  "dog_and_duck/quack/fault_messages.clj":
-  [null, 1, null, null, null, null, null, null, null, null, null, null,
-   null, null, null, null, null, null, null, 1, null, 1, null, null,
-   null, null, null, null, null, null, null, null],
+   null, null, null, null, null, null, 1, null, null, null, null, 0,
+   null, 6, 6, null, 0, null, 0, null, 4, null, 1, null, null, 12, 12,
+   12, 12, 9, 8, 4, null, 1, null, 0, 3, null, 0, 0, 0, 0, null, 1,
+   null, null, 3, 3, 3, 3, 1, 3, 3, 3, 3, null, 1, null, null, null,
+   null, 0, 0, 0, 0, 0, 0, null, null, null, 1, null, null, null, true,
+   0, true, true, null, 1, null, null, null, 1, null, 1, null, null, 1,
+   null, 1, 0, null, null, null, 1, null, null, 1, 1, 1, 1, null, 1, 1,
+   1, 1, 1, 1, null, 1, 1, 1, 0, null, null, null, 1, 1, 1, 1, 1, 1, 1,
+   1, 1, 1, 1, 1, 1, 1, 1, 1, null, 1, null, null, null, null, null,
+   null, null, 0, 0, 0, null, 0, 0, 0, 0, null, 1, null, 0, 0, 0, 0, 0,
+   0, 0, null, 1, null, null, null, null, null, null, null, null, null,
+   null, null, null, null, null, null, 0, 0, 0, 0, 0, 0, null, null,
+   null, null, null, 0, 0, 0, 0, null, null, 0, 0],
   "dog_and_duck/scratch/core.clj":
   [null, 1, null, null, null, null, null, null, null, null, null, null,
    null, null, null, null, null, null, null, 1, null, null, 0],
@@ -103,4 +122,10 @@
    null, null, null, null, null, null, null, null, null, null, null,
    null, null, 1, null, 1, 1, 1, null, null, null, null, 1, null, 1, 1,
    1, 1, null, null, 1, null, null, null, null, null, null, null, 1, 1,
-   1, 1, 1, 1, null, null]}}
+   1, 1, 1, 1, null, null],
+  "dog_and_duck/quack/picky/collections.clj":
+  [null, 1, null, null, null, null, null, null, null, null, null, null,
+   null, null, null, null, null, null, null, null, null, null, null,
+   null, 1, null, null, null, 0, 0, 0, 0, 0, 0, null, 1, null, null,
+   null, 0, 0, 0, 0, 0, 0, 0, null, 1, null, 0, 0, 0, 0, 0, null, null,
+   0, 0]}}
diff --git a/docs/cloverage/coverage.xml b/docs/cloverage/coverage.xml
index 6417165..a91e783 100644
--- a/docs/cloverage/coverage.xml
+++ b/docs/cloverage/coverage.xml
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/cloverage/dog_and_duck/quack/picky.clj.html b/docs/cloverage/dog_and_duck/quack/picky.clj.html
index 18b3ae0..9364e58 100644
--- a/docs/cloverage/dog_and_duck/quack/picky.clj.html
+++ b/docs/cloverage/dog_and_duck/quack/picky.clj.html
@@ -92,1558 +92,670 @@
                 029                                ActivityStreams spec."
                 
 
-                030      (:require [clojure.set :refer [intersection]]
+                030      (:require [dog-and-duck.quack.picky.collections :refer [collection-page-faults
                 
 
-                031                [dog-and-duck.quack.fault-messages :refer [messages]]
+                031                                                              paged-collection-faults
                 
 
-                032                [dog-and-duck.utils.process :refer [get-hostname get-pid]]
+                032                                                              simple-collection-faults]]
                 
 
-                033                [taoensso.timbre :as timbre
+                033                [dog-and-duck.quack.picky.constants :refer [actor-types]]
                 
 
-                034        ;; Optional, just refer what you like:
+                034                [dog-and-duck.quack.picky.utils :refer [any-or-faults
                 
 
-                035                 :refer [warn]]
+                035                                                        coll-object-reference-or-fault
                 
 
-                036                [clojure.data.json :as json])
+                036                                                        concat-non-empty
                 
 
-                037      (:import [java.net URI URISyntaxException]))
-                
-
-                038  
+                037                                                        has-activity-type?
                 
 
-                039  ;;;     Copyright (C) Simon Brooke, 2022
-                
-
-                040  
+                038                                                        has-actor-type? has-type?
                 
 
-                041  ;;;     This program is free software; you can redistribute it and/or
+                039                                                        has-type-or-fault
                 
 
-                042  ;;;     modify it under the terms of the GNU General Public License
+                040                                                        make-fault-object
                 
 
-                043  ;;;     as published by the Free Software Foundation; either version 2
+                041                                                        object-faults
                 
 
-                044  ;;;     of the License, or (at your option) any later version.
+                042                                                        object-reference-or-faults
+                
+
+                043                                                        string-or-fault]])
+                
+
+                044      (:import [java.net URI URISyntaxException]))
                 
 
                 045  
                 
 
-                046  ;;;     This program is distributed in the hope that it will be useful,
-                
-
-                047  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
-                
-
-                048  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-                
-
-                049  ;;;     GNU General Public License for more details.
+                046  ;;;     Copyright (C) Simon Brooke, 2022
                 
 
-                050  
+                047  
                 
 
-                051  ;;;     You should have received a copy of the GNU General Public License
+                048  ;;;     This program is free software; you can redistribute it and/or
                 
 
-                052  ;;;     along with this program; if not, write to the Free Software
+                049  ;;;     modify it under the terms of the GNU General Public License
                 
 
-                053  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+                050  ;;;     as published by the Free Software Foundation; either version 2
+                
+
+                051  ;;;     of the License, or (at your option) any later version.
                 
 
-                054  
+                052  
                 
 
-                055  ;; ERRATA
+                053  ;;;     This program is distributed in the hope that it will be useful,
+                
+
+                054  ;;;     but WITHOUT ANY WARRANTY; without even the implied warranty of
+                
+
+                055  ;;;     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+                
+
+                056  ;;;     GNU General Public License for more details.
                 
 
-                056  
-                
-
-                057  (def ^:dynamic *reify-refs*
+                057  
                 
 
-                058    "If `true`, references to objects in fields will be reified and validated. 
+                058  ;;;     You should have received a copy of the GNU General Public License
                 
 
-                059     If `false`, they won't, but an `:info` level fault report will be generated.
+                059  ;;;     along with this program; if not, write to the Free Software
                 
 
-                060     
+                060  ;;;     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+                
+
+                061  
+                
+
+                062  (defn uri-or-fault
                 
 
-                061     There are several things in the spec which, in a document, may correctly be
+                063    "If `u` is not a valid URI, return a fault object with this `severity` and 
                 
 
-                062     either
+                064     `if-invalid-token`. If it's `nil`, return a fault object with this `severity`
                 
 
-                063     
+                065     and `if-missing-token`. Otherwise return nil."
                 
 
-                064     1. a fully fleshed out object, or
+                066    ([u severity if-missing-token]
+                
+
+                067     (uri-or-fault u severity if-missing-token if-missing-token))
                 
 
-                065     2. a URI pointing to such an object.
+                068    ([u severity if-missing-token if-invalid-token]
+                
+
+                069     (try
+                
+
+                070       (if (uri? (URI. u))
                 
 
-                066     
+                071         nil
+                
+
+                072         (make-fault-object severity if-invalid-token))
                 
 
-                067     Obviously to fully validate a document we ought to reify all the refs and 
+                073       (catch URISyntaxException _
+                
+
+                074         (make-fault-object severity if-invalid-token))
                 
 
-                068     check that they are themselves valid, but
+                075       (catch NullPointerException _
                 
-
-                069     
-                
-
-                070     a. in some of the published test documents the URIs do not reference a
-                
-
-                071        valid document;
-                
-
-                072     b. there will be performance costs to reifying all the refs;
-                
-
-                073     c. in perverse cases, reifying refs might result in runaway recursion.
-                
-
-                074     
-                
-
-                075     TODO: I think that in production this should default to `true`."
-                
-
-                076    false)
+
+                076         (make-fault-object severity if-missing-token)))))
                 
 
                 077  
                 
-
-                078  (def ^:dynamic *reject-severity*
-                
-
-                079    "The severity at which the binary validator will return `false`.
-                
-
-                080     
-                
-
-                081     In practice documents seen in the wild do not typically appear to be 
-                
-
-                082     fully valid, and this does not matter. This allows the sensitivity of
-                
-
-                083     the binary validator (`dog-and-duck.quack.quack`) to be tuned. It's in
-                
-
-                084     this (`dog-and-duck.quack.picky`) namespace, not that one, because this
-                
-
-                085     namespace is where concerns about severity are handled."
-                
-
-                086    :must)
-                
-
-                087  
-                
 
-                088  (def ^:const context-key
+                078  (defn persistent-object-faults
                 
 
-                089    "The Clojure reader barfs on `:@context`, although it is in principle a valid 
+                079    "Return a list of faults found in persistent object `x`, or `nil` if none are."
                 
 
-                090     keyword. So we'll make it once, here, to make the code more performant and
+                080    ([x]
                 
-
-                091     easier to read."
+
+                081     (concat-non-empty
                 
 
-                092    (keyword "@context"))
+                082      (object-faults x)
                 
-
-                093  
+
+                083      (list
                 
-
-                094  (def ^:const severity
-                
-
-                095    "Severity of faults found, as follows:
-                
-
-                096     
-                
-
-                097     0. `:info` not actually a fault, but an issue noted during validation;
-                
-
-                098     1. `:minor` things which I consider to be faults, but which 
-                
-
-                099        don't actually breach the spec;
-                
-
-                100     2. `:should` instances where the spec says something SHOULD
-                
-
-                101        be done, which isn't;
-                
-
-                102     3. `:must` instances where the spec says something MUST
-                
-
-                103        be done, which isn't;
-                
-
-                104     4. `:critical` instances where I believe the fault means that
-                
-
-                105        the object cannot be meaningfully processed."
+
+                084       (if (contains? x :id)
                 
 
-                106    #{:info :minor :should :must :critical})
+                085         (try (let [id (URI. (:id x))]
                 
-
-                107  
-                
-
-                108  (def ^:const severity-filters
-                
-
-                109    "Hack for implementing a severity hierarchy"
-                
-
-                110    {:all #{}
-                
-
-                111     :info #{}
-                
-
-                112     :minor #{:info}
-                
-
-                113     :should #{:info :minor}
+
+                086                (when-not (= (.getScheme id) "https")
                 
 
-                114     :must #{:info :minor :should}
-                
-
-                115     :critical severity})
-                
-
-                116  
-                
-
-                117  (defn truthy?
+                087                  (make-fault-object :should :id-not-https)))
                 
 
-                118    "Return `true` if `x` is truthy, else `false`."
+                088              (catch URISyntaxException _
+                
+
+                089                (make-fault-object :must :id-not-uri))
                 
 
-                119    [x]
+                090              (catch NullPointerException _
                 
 
-                120    (if x true false))
+                091                (make-fault-object :must :null-id-persistent)))
                 
-
-                121  
-                
-
-                122  (defn has-type?
+
+                092         (make-fault-object :must :no-id-persistent)))))
                 
 
-                123    "Return `true` if object `x` has type `type`, else `false`.
-                
-
-                124     
-                
-
-                125     The values of `type` fields of ActivityStreams objects may be lists; they
-                
-
-                126     are considered to have a type if the type token is a member of the list."
-                
-
-                127    [x type]
-                
-
-                128    (assert (map? x) (string? type))
-                
-
-                129    (let [tv (:type x)]
+                093    ([x types severity token]
                 
 
-                130      (cond
-                
-
-                131        (coll? tv) (truthy? (not-empty (filter #(= % type) tv)))
+                094     (concat-non-empty
                 
 
-                132        :else (= tv type))))
+                095      (persistent-object-faults x)
                 
-
-                133  
-                
-
-                134  (defn filter-severity
-                
-
-                135    "Return a list of reports taken from these `reports` where the severity
-                
-
-                136     of the report is greater than this or equal to this `severity`."
-                
-
-                137    [reports severity]
-                
-
-                138    (cond (nil? reports) nil
-                
-
-                139          (and
-                
-
-                140           (coll? reports)
-                
-
-                141           (every? map? reports)
-                
-
-                142           (every? :severity reports)) (remove
-                
-
-                143                                        #((severity-filters severity) (:severity %))
-                
-
-                144                                        reports)
-                
-
-                145          :else
-                
-
-                146          (throw
-                
-
-                147           (ex-info
-                
-
-                148            "Argument `reports` was not a collection of fault reports"
+
+                096      (list
                 
 
-                149            {:arguments {:reports reports
-                
-
-                150                         :severity severity}}))))
+                097       (has-type-or-fault x types severity token)))))
                 
 
-                151  
-                
-
-                152  (def ^:const activitystreams-context-uri
-                
-
-                153    "The URI of the context of an ActivityStreams object is expected to be this
-                
-
-                154     literal string."
-                
-
-                155    "https://www.w3.org/ns/activitystreams")
-                
-
-                156  
-                
-
-                157  (def ^:const validation-fault-context-uri
-                
-
-                158    "The URI of the context of a validation fault report object shall be this
-                
-
-                159     literal string."
-                
-
-                160    "https://simon-brooke.github.io/dog-and-duck/codox/Validation_Faults.html")
-                
-
-                161  
+                098  
                 
 
-                162  (defn context?
+                099  (defn actor-faults
                 
 
-                163    "Returns `true` iff `x` quacks like an ActivityStreams context, else false.
+                100    "Return a list of faults found in actor `x`, or `nil` if none are."
                 
 
-                164     
+                101    [x]
                 
-
-                165     A context is either
-                
-
-                166     1. the URI (actually an IRI) `activitystreams-context-uri`, or
-                
-
-                167     2. a collection comprising that URI and a map."
-                
-
-                168    [x]
-                
-
-                169    (cond
+
+                102    (concat-non-empty
                 
 
-                170      (nil? x) false
+                103     (persistent-object-faults x)
                 
-
-                171      (string? x) (and (= x activitystreams-context-uri) true)
+
+                104     (list
                 
-
-                172      (coll? x) (and (context? (first (remove map? x)))
+
+                105      (when-not (has-actor-type? x)
                 
 
-                173                     (= (count x) 2)
-                
-
-                174                     true)
-                
-
-                175      :else false))
-                
-
-                176  
-                
-
-                177  (defmacro has-context?
-                
-
-                178    "True if `x` is an ActivityStreams object with a valid context, else `false`."
-                
-
-                179    [x]
-                
-
-                180    `(context? (context-key ~x)))
-                
-
-                181  
-                
-
-                182  (defn make-fault-object
-                
-
-                183    "Return a fault object with these `severity`, `fault` and `narrative` values.
-                
-
-                184     
-                
-
-                185     An ActivityPub object MUST have a globally unique ID. Whether this is 
-                
-
-                186     meaningful depends on whether we persist fault report objects and serve
-                
-
-                187     them, which at present I have no plans to do."
-                
-
-                188    ;; TODO: should not pass in the narrative; instead should use the :fault value
-                
-
-                189    ;; to look up the narrative in a resource file.
-                
-
-                190    [severity fault]
-                
-
-                191    (assoc {}
-                
-
-                192           context-key validation-fault-context-uri
+                106        (make-fault-object :must :not-actor-type))
                 
 
-                193           :id (str "https://"
-                
-
-                194                    (get-hostname)
-                
-
-                195                    "/fault/"
-                
-
-                196                    (get-pid)
-                
-
-                197                    ":"
+                107      (uri-or-fault
                 
 
-                198                    (inst-ms (java.util.Date.)))
+                108       (:inbox x) :must :no-inbox :invalid-inbox-uri)
                 
-
-                199           :type "Fault"
+
+                109      (uri-or-fault
+                
+
+                110       (:outbox x) :must :no-outbox :invalid-outbox-uri))))
+                
+
+                111  
                 
 
-                200           :severity severity
-                
-
-                201           :fault fault
-                
-
-                202           :narrative (or (messages fault)
+                112  (defn link-faults
                 
 
-                203                          (do
+                113    "A link object is required to have an `href` property. It may have all of
                 
-
-                204                            (warn "No narrative provided for fault token " fault)
+
+                114     `rel` | `mediaType` | `name` | `hreflang` | `height` | `width` | `preview`
+                
+
+                115     but I *think* they're all optional."
+                
+
+                116    [x]
+                
+
+                117    (concat-non-empty
+                
+
+                118     (object-reference-or-faults x "Link" :critical :expected-link)
+                
+
+                119     (list
+                
+
+                120      (uri-or-fault
                 
 
-                205                            (str fault)))))
+                121       (:href x) :must :no-href-uri :invalid-href-uri)
+                
+
+                122      (string-or-fault (:mediaType x) :minor :no-media-type #"\w+\/[-+.\w]+")
+                
+
+                123     ;; TODO: possibly more here. Audit against the specs
+                
+
+                124      )))
+                
+
+                125  
+                
+
+                126  (def ^:const base-activity-required-properties
+                
+
+                127    "Properties most activities should have. Values are validating functions, each.
+                
+
+                128     
+                
+
+                129     See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-activity"
+                
+
+                130    {:summary (fn [v] (when-not (string? v)
+                
+
+                131                        (list (make-fault-object :should :no-summary))))
+                
+
+                132     :actor (fn [v] (object-reference-or-faults v actor-types :must :no-actor))
+                
+
+                133     :object (fn [v] (object-reference-or-faults v nil :must :no-object))})
+                
+
+                134  
+                
+
+                135  (def ^:const intransitive-activity-required-properties
+                
+
+                136    "Properties intransitive activities should have.
+                
+
+                137     
+                
+
+                138     See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-intransitiveactivity"
+                
+
+                139    (dissoc base-activity-required-properties :object))
+                
+
+                140  
+                
+
+                141  (def ^:const accept-required-properties
+                
+
+                142    "As base-activity-required-properties, except that the type of the object
+                
+
+                143     is restricted."
+                
+
+                144    (assoc base-activity-required-properties
+                
+
+                145           :object
+                
+
+                146           (fn [v]
+                
+
+                147             (object-reference-or-faults v #{"Invite" "Person"}
+                
+
+                148                                         :must
+                
+
+                149                                         :bad-accept-target))))
+                
+
+                150  
+                
+
+                151  (def ^:const activity-required-properties
+                
+
+                152    "Properties activities should have, keyed by activity type. Values are maps 
+                
+
+                153     of the format of `base-activity-required-properties`, q.v."
+                
+
+                154    {"Accept" accept-required-properties
+                
+
+                155     "Add" base-activity-required-properties
+                
+
+                156     "Announce" base-activity-required-properties
+                
+
+                157     "Arrive" intransitive-activity-required-properties
+                
+
+                158     ;; TODO: is `:location` required for arrive?
+                
+
+                159     "Block" base-activity-required-properties
+                
+
+                160     "Create" base-activity-required-properties
+                
+
+                161     "Delete" base-activity-required-properties
+                
+
+                162     "Dislike" base-activity-required-properties
+                
+
+                163     "Flag" base-activity-required-properties
+                
+
+                164     "Follow" base-activity-required-properties
+                
+
+                165     ;; TODO: is `:object` required to be an actor?
+                
+
+                166     "Ignore" base-activity-required-properties
+                
+
+                167     "Invite" (assoc base-activity-required-properties :target
+                
+
+                168                     (fn [v]
+                
+
+                169                       (coll-object-reference-or-fault v #{"Event" "Group"}
+                
+
+                170                                                       :must
+                
+
+                171                                                       :bad-accept-target)))
+                
+
+                172     ;; TODO: are here other things one could meaningfully be invited to?
+                
+
+                173     "Join" base-activity-required-properties
+                
+
+                174     "Leave" base-activity-required-properties
+                
+
+                175     "Like" base-activity-required-properties
+                
+
+                176     "Listen" base-activity-required-properties
+                
+
+                177     "Move" base-activity-required-properties
+                
+
+                178     "Offer" base-activity-required-properties
+                
+
+                179     "Question" intransitive-activity-required-properties
+                
+
+                180     "Reject" base-activity-required-properties
+                
+
+                181     "Read" base-activity-required-properties
+                
+
+                182     "Remove" base-activity-required-properties
+                
+
+                183     "TentativeReject" base-activity-required-properties
+                
+
+                184     "TentativeAccept" accept-required-properties
+                
+
+                185     "Travel" base-activity-required-properties
+                
+
+                186     "Undo" base-activity-required-properties
+                
+
+                187     "Update" base-activity-required-properties
+                
+
+                188     "View" base-activity-required-properties})
+                
+
+                189  
+                
+
+                190  (defn activity-type-faults
+                
+
+                191    "Return a list of faults found in the activity `x`; if `type` is also 
+                
+
+                192     specified, it should be a string naming a specific activity type for
+                
+
+                193     which checks should be performed.
+                
+
+                194     
+                
+
+                195     Some specific activity types have specific requirements which are not
+                
+
+                196     requirements."
+                
+
+                197    ([x]
+                
+
+                198     (if (coll? (:type x))
+                
+
+                199       (map #(activity-type-faults x %) (:type x))
+                
+
+                200       (activity-type-faults x (:type x))))
+                
+
+                201    ([x type]
+                
+
+                202     (let [checks (activity-required-properties type)]
+                
+
+                203       (map
+                
+
+                204        #(apply (checks %) (x %))
+                
+
+                205        (keys checks)))))
                 
 
                 206  
                 
-
-                207  (defmacro nil-if-empty
-                
-
-                208    "if `x` is an empty collection, return `nil`; else return `x`."
-                
-
-                209    [x]
-                
-
-                210    `(if (and (coll? ~x) (empty? ~x)) nil
-                
 
-                211         ~x))
-                
-
-                212  
-                
-
-                213  (defn has-type-or-fault
+                207  (defn activity-faults
                 
 
-                214    "If object `x` has a `:type` value which is `acceptable`, return `nil`;
-                
-
-                215     else return a fault object with this `severity` and `token`.
-                
-
-                216     
-                
-
-                217     `acceptable` may be passed as either nil, a string, or a set of strings.
-                
-
-                218     If `acceptable` is `nil`, no type specific tests will be performed."
-                
-
-                219    [x acceptable severity token]
-                
-
-                220    (when acceptable
-                
-
-                221      (let [tv (:type x)]
-                
-
-                222        (when-not
+                208    [x]
                 
 
-                223         (cond
+                209    (concat-non-empty (persistent-object-faults x)
                 
-
-                224           (and (string? tv) (string? acceptable)) (= tv acceptable)
-                
-
-                225           (and (string? tv) (set? acceptable)) (acceptable tv)
-                
-
-                226           (and (coll? tv) (string? acceptable)) ((set tv) acceptable)
-                
-
-                227           (and (coll? tv) (set? acceptable)) (not-empty
-                
-
-                228                                               (intersection (set tv) acceptable))
-                
-
-                229           :else
-                
-
-                230           (throw (ex-info "Type value or `acceptable` argument not as expected."
-                
-
-                231                           {:arguments {:x x
-                
-
-                232                                        :acceptable acceptable
-                
-
-                233                                        :severity severity
-                
-
-                234                                        :token token}})))
-                
-
-                235          (make-fault-object severity token)))))
-                
-
-                236  
-                
-
-                237  (defn object-faults
-                
-
-                238    "Return a list of faults found in object `x`, or `nil` if none are.
-                
-
-                239     
-                
-
-                240     If `expected-type` is also passed, verify that `x` has `expected-type`.
-                
-
-                241     `expected-type` may be passed as a string or as a set of strings."
-                
-
-                242    ([x]
-                
-
-                243     (nil-if-empty
-                
-
-                244      (remove empty?
-                
-
-                245              (list
-                
-
-                246               (when-not (map? x)
-                
-
-                247                 (make-fault-object :critical :not-an-object))
-                
-
-                248               (when-not
-                
-
-                249                (has-context? x)
-                
-
-                250                 (make-fault-object :should :no-context))
-                
-
-                251               (when-not (:type x)
-                
-
-                252                 (make-fault-object :minor :no-type))
-                
-
-                253               (when-not (and (map? x) (contains? x :id))
-                
-
-                254                 (make-fault-object :minor :no-id-transient))))))
-                
-
-                255    ([x expected-type]
-                
-
-                256     (nil-if-empty
-                
-
-                257      (remove empty?
-                
-
-                258              (concat
-                
-
-                259               (object-faults x)
-                
-
-                260               (list
-                
-
-                261                ;; TODO: should resolve the correct `-faults`function for the
-                
-
-                262                ;; `expected-type` and call that; but that's for later.
-                
-
-                263                (has-type-or-fault x expected-type :critical :unexpected-type)))))))
-                
-
-                264  
-                
-
-                265  (defn uri-or-fault
-                
-
-                266    "If `u` is not a valid URI, return a fault object with this `severity` and 
-                
-
-                267     `if-invalid-token`. If it's `nil`, return a fault object with this `severity`
-                
-
-                268     and `if-missing-token`. Otherwise return nil."
-                
-
-                269    ([u severity if-missing-token]
-                
-
-                270     (uri-or-fault u severity if-missing-token if-missing-token))
-                
-
-                271    ([u severity if-missing-token if-invalid-token]
-                
-
-                272     (try
-                
-
-                273       (if (uri? (URI. u))
-                
-
-                274         nil
-                
-
-                275         (make-fault-object severity if-invalid-token))
-                
-
-                276       (catch URISyntaxException _
-                
-
-                277         (make-fault-object severity if-invalid-token))
-                
-
-                278       (catch NullPointerException _
-                
-
-                279         (make-fault-object severity if-missing-token)))))
-                
-
-                280  
-                
-
-                281  (defn persistent-object-faults
-                
-
-                282    "Return a list of faults found in persistent object `x`, or `nil` if none are."
-                
-
-                283    [x]
-                
-
-                284    (nil-if-empty
-                
-
-                285     (remove empty?
-                
-
-                286             (concat
-                
-
-                287              (object-faults x)
-                
-
-                288              (list
-                
-
-                289               (if (contains? x :id)
-                
-
-                290                 (try (let [id (URI. (:id x))]
-                
-
-                291                        (when-not (= (.getScheme id) "https")
-                
-
-                292                          (make-fault-object :should :id-not-https)))
-                
-
-                293                      (catch URISyntaxException _
-                
-
-                294                        (make-fault-object :must :id-not-uri))
-                
-
-                295                      (catch NullPointerException _
-                
-
-                296                        (make-fault-object :must :null-id-persistent)))
-                
-
-                297                 (make-fault-object :must :no-id-persistent)))))))
-                
-
-                298  
-                
-
-                299  (def ^:const actor-types
-                
-
-                300    "The set of types we will accept as actors.
-                
-
-                301     
-                
-
-                302     There's an [explicit set of allowed actor types]
-                
-
-                303     (https://www.w3.org/TR/activitystreams-vocabulary/#actor-types)."
-                
-
-                304    #{"Application"
-                
-
-                305      "Group"
-                
-
-                306      "Organization"
-                
-
-                307      "Person"
-                
-
-                308      "Service"})
-                
-
-                309  
-                
-
-                310  (defn actor-type?
-                
-
-                311    "Return `true` if the `x` is a recognised actor type, else `false`."
-                
-
-                312    [^String x]
-                
-
-                313    (if (actor-types x) true false))
-                
-
-                314  
-                
-
-                315  (defn has-actor-type?
-                
-
-                316    "Return `true` if the object `x` has a type which is an actor type, else 
-                
-
-                317     `false`."
-                
-
-                318    [x]
-                
-
-                319    (let [tv (:type x)]
-                
-
-                320      (cond
-                
-
-                321        (coll? tv) (truthy? (not-empty (filter actor-type? tv)))
-                
-
-                322        :else (actor-type? tv))))
-                
-
-                323  
-                
-
-                324  (defn actor-faults
-                
-
-                325    "Return a list of faults found in actor `x`, or `nil` if none are."
-                
-
-                326    [x]
-                
-
-                327    (nil-if-empty
-                
-
-                328     (remove empty?
-                
-
-                329             (concat (persistent-object-faults x)
-                
-
-                330                     (list
-                
-
-                331                      (when-not (has-actor-type? x)
-                
-
-                332                        (make-fault-object :must :not-actor-type))
-                
-
-                333                      (uri-or-fault
-                
-
-                334                       (:inbox x) :must :no-inbox :invalid-inbox-uri)
-                
-
-                335                      (uri-or-fault
-                
-
-                336                       (:outbox x) :must :no-outbox :invalid-outbox-uri))))))
-                
-
-                337  
-                
-
-                338  (def ^:const verb-types
-                
-
-                339    "The set of types we will accept as verbs.
-                
-
-                340     
-                
-
-                341     There's an [explicit set of allowed verb types]
-                
-
-                342     (https://www.w3.org/TR/activitystreams-vocabulary/#activity-types)."
-                
-
-                343    #{"Accept" "Add" "Announce" "Arrive" "Block" "Create" "Delete" "Dislike"
-                
-
-                344      "Flag" "Follow" "Ignore" "Invite" "Join" "Leave" "Like" "Listen" "Move"
-                
-
-                345      "Offer" "Question" "Reject" "Read" "Remove" "TentativeAccept"
-                
-
-                346      "TentativeReject" "Travel" "Undo" "Update" "View"})
-                
-
-                347  
-                
-
-                348  (defn verb-type?
-                
-
-                349    "`true` if `x`, a string, represents a recognised ActivityStreams activity
-                
-
-                350     type."
-                
-
-                351    [^String x]
-                
-
-                352    (if (verb-types x) true false))
-                
-
-                353  
-                
-
-                354  (defn has-activity-type?
-                
-
-                355    "Return `true` if the object `x` has a type which is an activity type, else 
-                
-
-                356     `false`."
-                
-
-                357    [x]
-                
-
-                358    (let [tv (:type x)]
+
+                210                      (activity-type-faults x)
                 
 
-                359      (cond
-                
-
-                360        (coll? tv) (truthy? (not-empty (filter verb-type? tv)))
+                211                      (list
                 
 
-                361        :else (actor-type? tv))))
+                212                       (when-not
                 
-
-                362  
-                
-
-                363  (defn string-or-fault
-                
-
-                364    "If this `value` is not a string, return a fault object with this `severity` 
-                
-
-                365     and `token`, else `nil`. If `pattern` is also passed, it is expected to be
-                
-
-                366     a Regex, and the fault object will be returned unless `value` matches the 
-                
-
-                367     `pattern`."
-                
-
-                368    ([value severity token]
-                
-
-                369     (when-not (string? value) (make-fault-object severity token)))
-                
-
-                370    ([value severity token pattern]
-                
-
-                371     (when not (and (string? value) (re-matches pattern value))
+
+                213                        (has-activity-type? x)
                 
 
-                372           (make-fault-object severity token))))
-                
-
-                373  
-                
-
-                374  (defn link-faults
-                
-
-                375    "A link object is required to have an `href` property. It may have all of
-                
-
-                376     `rel` | `mediaType` | `name` | `hreflang` | `height` | `width` | `preview`
-                
-
-                377     but I *think* they're all optional."
-                
-
-                378    [x]
-                
-
-                379    (list
-                
-
-                380     (uri-or-fault
-                
-
-                381      (:href x) :must :no-href-uri :invalid-href-uri)
-                
-
-                382     (string-or-fault (:mediaType x) :minor :no-media-type #"\w+\/[-+.\w]+")
-                
-
-                383     ;; TODO: possibly more here. Audit against the specs
-                
-
-                384     ))
-                
-
-                385  
-                
-
-                386  (defn object-reference-or-faults
-                
-
-                387    "If this `value` is either 
-                
-
-                388     
-                
-
-                389     1. an object of `expected-type`;
-                
-
-                390     2. a URI referencing an object of  `expected-type`; or
-                
-
-                391     3. a link object referencing an object of  `expected-type`
-                
-
-                392     
-                
-
-                393     and no faults are returned from validating the linked object, then return
-                
-
-                394     `nil`; else return a sequence comprising a fault object with this `severity`
-                
-
-                395     and `token`, prepended to the faults returned.
-                
-
-                396     
-                
-
-                397     As with `has-type-or-fault` (q.v.), `expected-type` may be passed as a
-                
-
-                398     string or as a set of strings.
-                
-
-                399     
-                
-
-                400     **NOTE THAT** if `*reify-refs*` is `false`, referenced objects will not
-                
-
-                401     actually be checked."
-                
-
-                402    [value expected-type severity token]
-                
-
-                403    (let [faults (cond
-                
-
-                404                   (string? value) (try (let [uri (URI. value)
-                
-
-                405                                              object (when *reify-refs*
-                
-
-                406                                                       (json/read-str (slurp uri)))]
-                
-
-                407                                          (when object
-                
-
-                408                                            (object-faults object expected-type)))
-                
-
-                409                                        (catch URISyntaxException _
-                
-
-                410                                          (make-fault-object severity token)))
-                
-
-                411                   (map? value) (if (has-type? value "Link")
-                
-
-                412                                  (cond
-                
-
-                413                                    ;; if we were looking for a link and we've 
-                
-
-                414                                    ;; found a link, that's OK.
-                
-
-                415                                    (= expected-type "Link") nil
-                
-
-                416                                    (and (set? expected-type) (expected-type "Link")) nil
-                
-
-                417                                    :else
-                
-
-                418                                    (object-reference-or-faults
-                
-
-                419                                     (:href value) expected-type severity token))
-                
-
-                420                                  (object-faults value expected-type))
-                
-
-                421                   :else (throw
-                
-
-                422                          (ex-info
-                
-
-                423                           "Argument `value` was not an object or a link to an object"
-                
-
-                424                           {:arguments {:value value}
-                
-
-                425                            :expected-type expected-type
-                
-
-                426                            :severity severity
-                
-
-                427                            :token token})))]
-                
-
-                428      (when faults (cons (make-fault-object severity token) faults))))
-                
-
-                429  
-                
-
-                430  (defn link-faults
-                
-
-                431    "Return a list of faults found in the link `x`, or `nil` if none are found."
-                
-
-                432    [x]
-                
-
-                433    (object-reference-or-faults x "Link" :critical :expected-link))
-                
-
-                434  
-                
-
-                435  (defn coll-object-reference-or-fault
-                
-
-                436    "As object-reference-or-fault, except `value` argument may also be a list of
-                
-
-                437     objects and/or object references."
-                
-
-                438    [value expected-type severity token]
-                
-
-                439    (cond
-                
-
-                440      (map? value) (object-reference-or-faults value expected-type severity token)
-                
-
-                441      (coll? value) (nil-if-empty
-                
-
-                442                     (remove nil?
-                
-
-                443                             (reduce concat
-                
-
-                444                                     (map
-                
-
-                445                                      #(object-reference-or-faults
-                
-
-                446                                        % expected-type severity token)
-                
-
-                447                                      value))))
-                
-
-                448      :else (throw
-                
-
-                449             (ex-info
-                
-
-                450              "Argument `value` was not an object, a link to an object, nor a list of these."
-                
-
-                451              {:arguments {:value value}
-                
-
-                452               :expected-type expected-type
-                
-
-                453               :severity severity
-                
-
-                454               :token token}))))
-                
-
-                455  
-                
-
-                456  (def ^:const base-activity-required-properties
-                
-
-                457    "Properties most activities should have. Values are validating functions, each.
-                
-
-                458     
-                
-
-                459     See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-activity"
-                
-
-                460    {:summary (fn [v] (when-not (string? v)
-                
-
-                461                        (list (make-fault-object :should :no-summary))))
-                
-
-                462     :actor (fn [v] (object-reference-or-faults v actor-types :must :no-actor))
-                
-
-                463     :object (fn [v] (object-reference-or-faults v nil :must :no-object))})
-                
-
-                464  
-                
-
-                465  (def ^:const intransitive-activity-required-properties
-                
-
-                466    "Properties intransitive activities should have.
-                
-
-                467     
-                
-
-                468     See https://www.w3.org/TR/activitystreams-vocabulary/#dfn-intransitiveactivity"
-                
-
-                469    (dissoc base-activity-required-properties :object))
-                
-
-                470  
-                
-
-                471  (def ^:const accept-required-properties
-                
-
-                472    "As base-activity-required-properties, except that the type of the object
-                
-
-                473     is restricted."
-                
-
-                474    (assoc base-activity-required-properties
-                
-
-                475           :object
-                
-
-                476           (fn [v]
-                
-
-                477             (object-reference-or-faults v #{"Invite" "Person"}
-                
-
-                478                                         :must
-                
-
-                479                                         :bad-accept-target))))
-                
-
-                480  
-                
-
-                481  (def ^:const activity-required-properties
-                
-
-                482    "Properties activities should have, keyed by activity type. Values are maps 
-                
-
-                483     of the format of `base-activity-required-properties`, q.v."
-                
-
-                484    {"Accept" accept-required-properties
-                
-
-                485     "Add" base-activity-required-properties
-                
-
-                486     "Announce" base-activity-required-properties
-                
-
-                487     "Arrive" intransitive-activity-required-properties
-                
-
-                488     ;; TODO: is `:location` required for arrive?
-                
-
-                489     "Block" base-activity-required-properties
-                
-
-                490     "Create" base-activity-required-properties
-                
-
-                491     "Delete" base-activity-required-properties
-                
-
-                492     "Dislike" base-activity-required-properties
-                
-
-                493     "Flag" base-activity-required-properties
-                
-
-                494     "Follow" base-activity-required-properties
-                
-
-                495     ;; TODO: is `:object` required to be an actor?
-                
-
-                496     "Ignore" base-activity-required-properties
-                
-
-                497     "Invite" (assoc base-activity-required-properties :target
-                
-
-                498                     (fn [v]
-                
-
-                499                       (coll-object-reference-or-fault v #{"Event" "Group"}
-                
-
-                500                                                       :must
-                
-
-                501                                                       :bad-accept-target)))
-                
-
-                502     ;; TODO: are here other things one could meaningfully be invited to?
-                
-
-                503     "Join" base-activity-required-properties
-                
-
-                504     "Leave" base-activity-required-properties
-                
-
-                505     "Like" base-activity-required-properties
-                
-
-                506     "Listen" base-activity-required-properties
-                
-
-                507     "Move" base-activity-required-properties
-                
-
-                508     "Offer" base-activity-required-properties
-                
-
-                509     "Question" intransitive-activity-required-properties
-                
-
-                510     "Reject" base-activity-required-properties
-                
-
-                511     "Read" base-activity-required-properties
-                
-
-                512     "Remove" base-activity-required-properties
-                
-
-                513     "TentativeReject" base-activity-required-properties
-                
-
-                514     "TentativeAccept" accept-required-properties
-                
-
-                515     "Travel" base-activity-required-properties
-                
-
-                516     "Undo" base-activity-required-properties
-                
-
-                517     "Update" base-activity-required-properties
-                
-
-                518     "View" base-activity-required-properties})
-                
-
-                519  
-                
-
-                520  (defn activity-type-faults
-                
-
-                521    "Return a list of faults found in the activity `x`; if `type` is also 
-                
-
-                522     specified, it should be a string naming a specific activity type for
-                
-
-                523     which checks should be performed.
-                
-
-                524     
-                
-
-                525     Some specific activity types have specific requirements which are not
-                
-
-                526     requirements."
-                
-
-                527    ([x]
-                
-
-                528     (if (coll? (:type x))
-                
-
-                529       (map #(activity-type-faults x %) (:type x))
-                
-
-                530       (activity-type-faults x (:type x))))
-                
-
-                531    ([x type]
-                
-
-                532     (let [checks (activity-required-properties type)]
-                
-
-                533       (map
-                
-
-                534        #(apply (checks %) (x %))
-                
-
-                535        (keys checks)))))
-                
-
-                536  
-                
-
-                537  (defn activity-faults
-                
-
-                538    [x]
-                
-
-                539    (nil-if-empty
-                
-
-                540     (remove empty?
-                
-
-                541             (concat (persistent-object-faults x)
-                
-
-                542                     (activity-type-faults x)
-                
-
-                543                     (list
-                
-
-                544                      (when-not
-                
-
-                545                       (has-activity-type? x)
+                214                         (make-fault-object :must :not-activity-type))
                 
 
-                546                        (make-fault-object :must :not-activity-type))
+                215                       (when-not (string? (:summary x)) (make-fault-object :should :no-summary)))))
                 
-
-                547                      (when-not (string? (:summary x)) (make-fault-object :should :no-summary)))))))
+
+                216  
+                
+
+                217  (defn collection-faults
+                
+
+                218    "Return a list of faults found in the collection `x`; if `type` is also 
+                
+
+                219     specified, it should be a string naming a specific collection type for
+                
+
+                220     which checks should be performed. 
+                
+
+                221     
+                
+
+                222     Every collection *should*(?) have a `totalItems` field (an integer).
+                
+
+                223     
+                
+
+                224     Beyond that, collections are either 'just collections' (in which case
+                
+
+                225     they *should* have an `items` field (a sequence)), or else they're paged
+                
+
+                226     collections, in which case they *must*(?) have a `first` field which is 
+                
+
+                227     a collection page or a URI pointing to a collection page, and *should* 
+                
+
+                228     have a `last` field which is similar.
+                
+
+                229     
+                
+
+                230     The pages of collections *should* be collection pages; the pages of 
+                
+
+                231     ordered collections *should* be ordered collection pages."
+                
+
+                232    ([x]
+                
+
+                233     (collection-faults
+                
+
+                234      x
+                
+
+                235      (first
+                
+
+                236       (remove nil?
+                
+
+                237               (map #(when (has-type? x %) %)
+                
+
+                238                    ["Collection"
+                
+
+                239                     "OrderedCollection"
+                
+
+                240                     "CollectionPage"
+                
+
+                241                     "OrderedCollectionPage"])))))
+                
+
+                242    ([x type]
+                
+
+                243     ;; (log/info "collection-faults called with argumens " x ", " type)
+                
+
+                244     (case type
+                
+
+                245       ("Collection" "OrderedCollection") (any-or-faults
+                
+
+                246                                           (list (simple-collection-faults x type)
+                
+
+                247                                                 (paged-collection-faults x type))
+                
+
+                248                                           :must
+                
+
+                249                                           :no-items)
+                
+
+                250       ("CollectionPage" "OrderedCollectionPage") (collection-page-faults x type)
+                
+
+                251       (list (make-fault-object :critical :expected-collection)))))