Use typed UTF-8 byte helpers in SSD I/O

ober

1e6030493d0b04a12bc94690d9613583d114f24b

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 8704305..54b5dd0 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1579,13 +1579,13 @@
                 destinationNewFileDelta
                 storageStatsExceedsQuota storageCapacityWithinQuota
                 byteCountWithinLimit storageEntryBytesAllowed
-                positiveCount learnedExamplesFull
+                positiveCount positiveByteCount learnedExamplesFull
                 learnedExamplesShouldRebuild treeDepthExceeded
                 zipEntryCountExceeded zipExpandedBytesExceeded
                 zipCompressionRatioExceeded zipExportQuotaExceeded
                 treeExportQuotaExceeded
                 byteArraySizeBytes byteArrayBytesWithinLimit
-                textUtf8SizeBytes textUtf8BytesWithinLimit
+                textUtf8Bytes textUtf8SizeBytes textUtf8BytesWithinLimit
                 oldDestinationBytes urlEffectivePort
                 remoteBearerTokenPresent remoteBearerTokenValid
                 remotePinPresent remotePinOrNull remotePinSha256Length
@@ -1684,6 +1684,8 @@
           (byteCountWithinLimit bytes maxBytes))
         (def (positiveCount (count : Int32)) : Bool
           (> count (int32 0)))
+        (def (positiveByteCount (count : Int)) : Bool
+          (> count (int 0)))
         (def (learnedExamplesFull (count : Int32) (maxExamples : Int32)) : Bool
           (>= count maxExamples))
         (def (learnedExamplesShouldRebuild (imported : Int32)
@@ -1720,8 +1722,10 @@
           (int (bytevector-length bytes)))
         (def (byteArrayBytesWithinLimit (bytes : Bytes) (maxBytes : Int)) : Bool
           (<= (byteArraySizeBytes bytes) maxBytes))
+        (def (textUtf8Bytes (text : String)) : Bytes
+          (string->utf8 text))
         (def (textUtf8SizeBytes (text : String)) : Int
-          (byteArraySizeBytes (string->utf8 text)))
+          (byteArraySizeBytes (textUtf8Bytes text)))
         (def (textUtf8BytesWithinLimit (text : String) (maxBytes : Int)) : Bool
           (<= (textUtf8SizeBytes text) maxBytes))
         (def (urlEffectivePort (url : URL)) : Int32
@@ -7152,7 +7156,7 @@
        "    }"
        ""
        "    private fun sendClientLog(payload: JSONObject, readTimeoutMillis: Int) {"
-       "        val bytes = payload.toString().toByteArray(Charsets.UTF_8)"
+       "        val bytes = textUtf8Bytes(payload.toString())"
        "        val connection = (URL(CLIENT_LOG_ENDPOINT).openConnection() as HttpsURLConnection).apply {"
        "            requestMethod = \"POST\""
        "            connectTimeout = CLIENT_LOG_CONNECT_TIMEOUT_MS"
@@ -8109,7 +8113,7 @@
        "            private set"
        ""
        "        private fun account(amount: Int): Int {"
-       "            if (amount > 0) {"
+       "            if (positiveCount(amount)) {"
        "                count += amount.toLong()"
        "                if (count > limit) throw IllegalStateException(inputLimitExceededMessage(limit))"
        "            }"
@@ -8226,7 +8230,7 @@
        "    }"
        ""
        "    private fun atomicWriteText(destination: File, text: String) ="
-       "        atomicWrite(destination, text.toByteArray(Charsets.UTF_8))"
+       "        atomicWrite(destination, textUtf8Bytes(text))"
        ""
        "    private fun loadRemoteConfig(): RemoteConfig? {"
        "        val preferences = context.getSharedPreferences(\"ssd_review_remote\", Context.MODE_PRIVATE)"
@@ -8691,7 +8695,7 @@
        "                val name = child.name ?: return@forEach"
        "                if (!safeLeaf(name)) return@forEach"
        "                val remaining = MAX_ZIP_TOTAL_BYTES - budget.expandedBytes"
-       "                if (remaining <= 0L) throw IllegalStateException(\"Import tree exceeds byte limit\")"
+       "                if (!positiveByteCount(remaining)) throw IllegalStateException(\"Import tree exceeds byte limit\")"
        "                val bytes = context.contentResolver.openInputStream(child.uri)?.use {"
        "                    readBoundedBytes(it, boundedTreeReadLimit(MAX_ZIP_ENTRY_BYTES, remaining))"
        "                }"
@@ -8789,7 +8793,7 @@
        "        val payload = autosaveTruthPayloadJson(truth).toString()"
        "        thread(name = \"sfb-truth-autosave\") {"
        "            try {"
-       "                val bytes = payload.toByteArray(Charsets.UTF_8)"
+       "                val bytes = textUtf8Bytes(payload)"
        "                require(byteArrayBytesWithinLimit(bytes, MAX_HTTP_TEXT_BYTES)) { \"Autosave payload exceeds byte limit\" }"
        "                val connection = openPinnedConnection("
        "                    \"truth\","