Move SSD remote truth selection to typed Kotlin

ober

e78c614e73ca3c82a0a76b5113c2dd53176dd6c4

diff --git a/.build.yml b/.build.yml
index d50d04f..b258c1d 100644
--- a/.build.yml
+++ b/.build.yml
@@ -5,7 +5,7 @@ packages:
   - make=4.4.1-r4
 sources:
   # Build dependency: full immutable commit, mirrored in dependencies.lock.json.
-  - "https://git.sr.ht/~lisp/jerboa#d1dfda6682e7948e4f3e2c54029f01f69bd7109e"
+  - "https://git.sr.ht/~lisp/jerboa#7add929a45a7bbdfbd50dbea56b943bdcbb72e95"
   # The second source is the build subject selected by the SourceHut submitter.
   - https://git.sr.ht/~lisp/jerboa-android
 tasks:
@@ -14,6 +14,6 @@ tasks:
       test "$(apk info -v chez-scheme)" = chez-scheme-10.3.0-r2
       test "$(apk info -v git)" = git-2.54.0-r0
       test "$(apk info -v make)" = make-4.4.1-r4
-      test "$(git -C ../jerboa rev-parse HEAD)" = d1dfda6682e7948e4f3e2c54029f01f69bd7109e
-      test "$(git -C ../jerboa rev-parse 'HEAD^{tree}')" = 785fcb008694e534f1a7fb53dacd7893f5f4e04d
+      test "$(git -C ../jerboa rev-parse HEAD)" = 7add929a45a7bbdfbd50dbea56b943bdcbb72e95
+      test "$(git -C ../jerboa rev-parse 'HEAD^{tree}')" = fa69a6d72e022a099b2eca36884501beaa8155e2
       JERBOA="chez --libdirs .:../jerboa/lib --script" make test
diff --git a/dependencies.lock.json b/dependencies.lock.json
index 1426902..369516d 100644
--- a/dependencies.lock.json
+++ b/dependencies.lock.json
@@ -11,8 +11,8 @@
   "generator_runtime": {
     "name": "jerboa",
     "repository": "https://git.sr.ht/~lisp/jerboa",
-    "commit": "d1dfda6682e7948e4f3e2c54029f01f69bd7109e",
-    "tree": "785fcb008694e534f1a7fb53dacd7893f5f4e04d"
+    "commit": "7add929a45a7bbdfbd50dbea56b943bdcbb72e95",
+    "tree": "fa69a6d72e022a099b2eca36884501beaa8155e2"
   },
   "assurance_tools": {
     "osv_scanner": {
diff --git a/scripts/verify-supply-chain.sh b/scripts/verify-supply-chain.sh
index a44ad19..968fcd0 100755
--- a/scripts/verify-supply-chain.sh
+++ b/scripts/verify-supply-chain.sh
@@ -3,8 +3,8 @@ set -eu
 
 repo=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)
 lock="$repo/dependencies.lock.json"
-jerboa_commit=d1dfda6682e7948e4f3e2c54029f01f69bd7109e # gitsafe:ignore
-jerboa_tree=785fcb008694e534f1a7fb53dacd7893f5f4e04d # gitsafe:ignore
+jerboa_commit=7add929a45a7bbdfbd50dbea56b943bdcbb72e95 # gitsafe:ignore
+jerboa_tree=fa69a6d72e022a099b2eca36884501beaa8155e2 # gitsafe:ignore
 gradle_sha=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
 jdk_macos_sha=8fa1eff40bb637a33613b2ccb8b12c70dc3661cc22cf8e784943715769a05336
 jdk_linux_sha=d8afc263758141a66e0e3aafc321e783f7016696f4eaea067d340a269037d331
diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 1c1860c..7f3a039 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -3446,6 +3446,48 @@
                   (json-object-put-json-object! cached "source" source)
                   cached)))))))
 
+    (typed-kotlin-file "com/sfb/ssdreview/RemoteTruthSelect.kt"
+      (kotlin-imports (org json JSONArray) (org json JSONObject))
+      (typed-library (com sfb ssdreview)
+        (export bestRemoteTruthForSession remoteTruthTimestamp)
+        (type JSONArray)
+        (type JSONObject)
+        (type Int)
+        (type Int32)
+        (def (remoteTruthTimestamp (truth : JSONObject)) : Int
+          (let ((generated (json-object-opt-int-default truth "generated_at" (int -1))))
+            (if (< generated (int 0))
+              (json-object-opt-int-default truth "created_at" (int 0))
+              generated)))
+        (def (betterRemoteTruth? (candidate : JSONObject) (current : JSONObject)) : Bool
+          (let ((candidateCount (truthGroupCount candidate))
+                (currentCount (truthGroupCount current)))
+            (or (> candidateCount currentCount)
+                (and (= candidateCount currentCount)
+                     (> (remoteTruthTimestamp candidate)
+                        (remoteTruthTimestamp current))))))
+        (def (betterNullableRemoteTruth (candidate : JSONObject)
+                                        (current : (Nullable JSONObject))) : (Nullable JSONObject)
+          (if (nullable-null? current)
+            (nullable-some candidate)
+            (if (betterRemoteTruth? candidate (nullable-get current))
+              (nullable-some candidate)
+              current)))
+        (def (remoteTruthEntryTruth (entry : JSONObject)) : (Nullable JSONObject)
+          (json-object-opt-json-object entry "truth"))
+        (def (bestRemoteTruthForSession (session : SsdSession)
+                                        (entries : JSONArray)) : (Nullable JSONObject)
+          (for/fold ((best (nullable-none JSONObject)))
+                    ((i (in-range (int32 0) (json-array-length entries))))
+            (let ((entry (json-array-opt-json-object entries i)))
+              (if (nullable-null? entry)
+                best
+                (let ((truth (remoteTruthEntryTruth (nullable-get entry))))
+                  (if (or (nullable-null? truth)
+                          (not (sameShipTruth session (nullable-get truth))))
+                    best
+                    (betterNullableRemoteTruth (nullable-get truth) best)))))))))
+
     (typed-kotlin-file "com/sfb/ssdreview/TruthCellIds.kt"
       (typed-library (com sfb ssdreview)
         (export uniqueTruthCellId)
@@ -6392,28 +6434,17 @@
        "            }"
        "            val entries = payload.optJSONArray(\"truths\") ?: JSONArray()"
        "            if (entries.length() > MAX_ZIP_ENTRIES) throw IllegalStateException(\"Too many remote truth entries\")"
-       "            var bestForSession: JSONObject? = null"
        "            var imported = 0"
        "            for (i in 0 until entries.length()) {"
        "                val entry = entries.getJSONObject(i)"
        "                val truth = entry.optJSONObject(\"truth\") ?: continue"
        "                val modified = entry.optLong(\"modified\", 0L)"
        "                if (writeRemoteTruth(truth, modified)) imported += 1"
-       "                if (sameShipTruth(session, truth)) {"
-       "                    val current = bestForSession"
-       "                    if ("
-       "                        current == null ||"
-       "                        truthGroupCount(truth) > truthGroupCount(current) ||"
-       "                        (truthGroupCount(truth) == truthGroupCount(current) &&"
-       "                            (truthTime(truth) ?: 0L) > (truthTime(current) ?: 0L))"
-       "                    ) {"
-       "                        bestForSession = truth"
-       "                    }"
-       "                }"
        "            }"
+       "            val bestForSession = bestRemoteTruthForSession(session, entries)"
        "            bestForSession?.let { truth ->"
        "                val cached = truthForSession(session, truth)"
-       "                if (writeRemoteTruth(cached, truthTime(truth) ?: 0L, replaceIfMoreComplete = true)) {"
+       "                if (writeRemoteTruth(cached, remoteTruthTimestamp(truth), replaceIfMoreComplete = true)) {"
        "                    imported += 1"
        "                }"
        "            }"