Generate SSD remote truth writes from typed Jerboa

ober

d96a4de0ef3160a37b3ad36f8b3561cd36cba031

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 9299f74..bb828f2 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -15549,7 +15549,8 @@
                 truthStoreEnsureStorageCapacityLocal
                 truthStoreLoadRemoteConfigLocal
                 truthStoreRemoteUrlLocal
-                truthStoreReadResponseTextLocal)
+                truthStoreReadResponseTextLocal
+                truthStoreWriteRemoteTruthLocal)
         (type Context)
         (type Exception)
         (type File)
@@ -15588,6 +15589,24 @@
                   (file : File)
                   (json : JSONObject)) : Unit
           (kotlin-member-call appendJsonLine))
+        (extern (truthStoreInvalidateTruthIndexRaw
+                  (store : TruthStore)) : Unit
+          (kotlin-member-call invalidateTruthIndex))
+        (extern (truthStoreAtomicWriteTextRaw
+                  (store : TruthStore)
+                  (destination : File)
+                  (text : String)) : Unit
+          (kotlin-member-call atomicWriteText))
+        (extern (truthStoreShouldReplaceLocalRaw
+                  (store : TruthStore)
+                  (local : File)
+                  (remoteText : String)
+                  (remoteModified : Int)) : Bool
+          (kotlin-member-call shouldReplaceLocal))
+        (extern (truthStoreJsonToPrettyString
+                  (json : JSONObject)
+                  (indent : Int32)) : String
+          (kotlin-member-call toString))
         (extern (truthStoreConnectionInputStream
                   (connection : HttpsURLConnection)) : InputStream
           (kotlin-member-get inputStream))
@@ -16038,7 +16057,47 @@
                     (readBoundedBytes
                       input
                       (truthStoreMaxHttpTextBytes)))
-                  (truthStoreInputStreamClose input))))))))
+                  (truthStoreInputStreamClose input))))))
+        (def (truthStoreWriteRemoteTruthLocal
+               (store : TruthStore)
+               (truth : JSONObject)
+               (modified : Int)
+               (replaceIfMoreComplete : Bool)) : Bool
+          (let ((validated (validatedSourceKey (truthSourceKey truth))))
+            (if (validatedSourceKeyPresent validated)
+              (let ((key (validatedSourceKeyOrEmpty validated)))
+                (let ((dest (truthStoreTruthPath store key))
+                      (text (truthStoreJsonToPrettyString truth (int32 2))))
+                  (let ((compareMoreComplete
+                          (shouldCompareMoreCompleteTruth
+                            replaceIfMoreComplete
+                            dest)))
+                    (let ((local
+                            (if compareMoreComplete
+                              (nullable-some
+                                (jsonObjectFromText
+                                  (truthStoreReadLocalText dest)))
+                              (nullable-none JSONObject))))
+                      (let ((shouldWrite
+                              (shouldWriteRemoteTruth
+                                compareMoreComplete
+                                (truthGroupCount truth)
+                                (nullableTruthGroupCount local)
+                                (truthStoreShouldReplaceLocalRaw
+                                  store
+                                  dest
+                                  text
+                                  modified))))
+                        (if (remoteTruthWriteSkipped shouldWrite)
+                          #f
+                          (begin
+                            (truthStoreAtomicWriteTextRaw
+                              store
+                              dest
+                              text)
+                            (truthStoreInvalidateTruthIndexRaw store)
+                            #t)))))))
+              #f)))))
 
     (kotlin-file-lines "com/sfb/ssdreview/TruthStore.kt"
       (
@@ -16116,7 +16175,7 @@
        "    }"
        ""
        "    @Synchronized"
-       "    private fun invalidateTruthIndex() {"
+       "    internal fun invalidateTruthIndex() {"
        "        truthIndexCacheLoaded = false"
        "        truthIndexCache = emptyList()"
        "    }"
@@ -16210,7 +16269,7 @@
        "        }"
        "    }"
        ""
-       "    private fun atomicWriteText(destination: File, text: String) ="
+       "    internal fun atomicWriteText(destination: File, text: String) ="
        "        atomicWrite(destination, textUtf8Bytes(text))"
        ""
        "    private fun loadRemoteConfig(): RemoteConfig? ="
@@ -16329,20 +16388,8 @@
        "        truth: JSONObject,"
        "        modified: Long,"
        "        replaceIfMoreComplete: Boolean = false"
-       "    ): Boolean {"
-       "        val validated = validatedSourceKey(truthSourceKey(truth))"
-       "        if (!validatedSourceKeyPresent(validated)) return false"
-       "        val key = validatedSourceKeyOrEmpty(validated)"
-       "        val dest = truthPath(key)"
-       "        val text = truth.toString(2)"
-       "        val compareMoreComplete = shouldCompareMoreCompleteTruth(replaceIfMoreComplete, dest)"
-       "        val local = if (compareMoreComplete) jsonObjectFromText(readLocalText(dest)) else null"
-       "        val shouldWrite = shouldWriteRemoteTruth(compareMoreComplete, truthGroupCount(truth), nullableTruthGroupCount(local), shouldReplaceLocal(dest, text, modified))"
-       "        if (remoteTruthWriteSkipped(shouldWrite)) return false"
-       "        atomicWriteText(dest, text)"
-       "        invalidateTruthIndex()"
-       "        return true"
-       "    }"
+       "    ): Boolean ="
+       "        truthStoreWriteRemoteTruthLocal(this, truth, modified, replaceIfMoreComplete)"
        ""
        "    fun applyLearnedGuesses(session: SsdSession): Int {"
        "        val file = File(learnedDir, learnedExamplesFileName())"
@@ -16691,7 +16738,7 @@
        "        atomicWriteText(file, merged)"
        "    }"
        ""
-       "    private fun shouldReplaceLocal(local: File, remoteText: String, remoteModified: Long): Boolean {"
+       "    internal fun shouldReplaceLocal(local: File, remoteText: String, remoteModified: Long): Boolean {"
        "        if (localFileMissing(local)) return true"
        "        val localText = readLocalText(local)"
        "        if (localTextMatchesRemote(localText, remoteText)) return false"