         (let [[part changes] (first inputs)]
           (if (and (nil? pending) (empty? changes))
             ; No pending records or changes, so use "pass through" logic.
-            ; FIXME: this will allow invalid partitions if the partition limit has changed!
-            ; TODO: check partition validity, redistribute if needed
-            (recur (conj result part) nil (next inputs))
+            (let [[parts pending] (check-partition store params limit half-full part)]
+              (recur (into result parts) pending (next inputs)))
+
             ; Load partition data or use virtual tablet records.
             (let [tablet (tablet/from-records (read-all store part nil))
                   tablet' (tablet/join pending (apply-patch tablet changes))]

    (< (count (tablet/read-all pending)) half-full)
      (if-let [prev (peek result)]
        ; Load last result link and redistribute into two valid partitions.
        (->> pending
             (tablet/join (tablet/from-records (read-all store prev nil)))
             (tablet/read-all)
             (partition-records store params)
             (into (pop result)))
        ; No siblings, so return virtual tablet for carrying.


;; ## Update Functions

; TODO: find better pattern than passing limit and half-full around :\

(defn- check-partition
  "Check a partition for validity under the current parameters. Returns a tuple
  containing output valid partitions and a new virtual tablet, if needed."
  [store params limit half-full part]
  (cond
    (< (::record/count part) half-full)
      [nil (tablet/from-records (read-all store part nil))]

    (< limit (::record/count part))
      [(partition-records store params (read-all store part nil)) nil]

    :else
      [[part] nil]))


(defn- apply-patch
  "Performs an update on the tablet by applying the patch changes. Returns an
  updated tablet, or nil if the result was empty."
