Move SSD truth identity reads to typed Kotlin

ober

885a973f1812730c2035706f5453b2cabd6223dd

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index d46a40e..428e29a 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1595,6 +1595,7 @@
       (typed-library (com sfb ssdreview)
         (export truthJsonArrayOrEmpty truthJsonObjectOrEmpty
                 truthGroupCount nullableTruthGroupCount truthSsdAreaRect
+                truthSourceKey truthSessionIdOr
                 imageWidth imageHeight)
         (type JSONArray)
         (type JSONObject)
@@ -1622,6 +1623,10 @@
               (truthGroupCount (nullable-get truth))))
         (def (truthSsdAreaRect (truth : JSONObject)) : (Nullable FloatArray)
           (jsonRect (json-object-opt-json-array truth "ssd_area")))
+        (def (truthSourceKey (truth : JSONObject)) : String
+          (json-object-opt-string truth "source_key"))
+        (def (truthSessionIdOr (truth : JSONObject) (fallback : String)) : String
+          (json-object-opt-string-default truth "session_id" fallback))
         (def (imageWidth (truth : JSONObject)) : Int32
           (positiveOrOne
             (json-object-opt-int32-default
@@ -6564,12 +6569,12 @@
        ""
        "    @Synchronized"
        "    fun saveTruthSnapshot(truth: JSONObject): File {"
-       "        val sourceKey = validatedSourceKey(truth.optString(\"source_key\"))"
+       "        val sourceKey = validatedSourceKey(truthSourceKey(truth))"
        "            ?: throw IllegalArgumentException(\"Invalid source key\")"
        "        val truthFile = truthPath(sourceKey)"
        "        atomicWriteText(truthFile, truth.toString(2))"
        "        invalidateTruthIndex()"
-       "        val sessionId = truth.optString(\"session_id\", sourceKey.ifBlank { \"unknown\" })"
+       "        val sessionId = truthSessionIdOr(truth, sourceKey.ifBlank { \"unknown\" })"
        "        val reviewFile = containedLeaf(reviewsDir, \"review-${sha256Hex(sessionId.toByteArray())}.review.json\")"
        "        atomicWriteText(reviewFile, truth.toString(2))"
        "        val event = snapshotEventJson(sessionId, sourceKey, truthFile.name)"
@@ -6888,7 +6893,7 @@
        "        modified: Long,"
        "        replaceIfMoreComplete: Boolean = false"
        "    ): Boolean {"
-       "        val key = validatedSourceKey(truth.optString(\"source_key\")) ?: return false"
+       "        val key = validatedSourceKey(truthSourceKey(truth)) ?: return false"
        "        val dest = truthPath(key)"
        "        val text = truth.toString(2)"
        "        val shouldWrite = if (replaceIfMoreComplete && dest.exists()) {"