Move SSD ZIP budget math to typed Kotlin

ober

80c659417f094c92823f7db15fdcf3ffb13d10c4

diff --git a/templates/ssd-review.ss b/templates/ssd-review.ss
index 9ef4ac7..c6141f1 100644
--- a/templates/ssd-review.ss
+++ b/templates/ssd-review.ss
@@ -1268,7 +1268,7 @@
                 make-StorageStats StorageStats?
                 StorageStats-files StorageStats-files-set!
                 StorageStats-bytes StorageStats-bytes-set!
-                storageSummaryText)
+                storageSummaryText compressionRatioBudget boundedTreeReadLimit)
         (type Int32)
         (record ImportBudget
           ((mut entries : Int32)
@@ -1276,6 +1276,16 @@
         (record StorageStats
           ((mut files : Int32)
            (mut bytes : Int)))
+        (def (maxIntValue (a : Int) (b : Int)) : Int
+          (if (> a b) a b))
+        (def (minIntValue (a : Int) (b : Int)) : Int
+          (if (< a b) a b))
+        (def (compressionRatioBudget (floorBytes : Int)
+                                     (compressedBytes : Int)
+                                     (ratio : Int)) : Int
+          (maxIntValue floorBytes (* compressedBytes ratio)))
+        (def (boundedTreeReadLimit (entryLimit : Int) (remaining : Int)) : Int
+          (minIntValue entryLimit remaining))
         (def (storageSummaryText (truthCount : Int32)
                                  (eventCount : Int32)) : String
           (string-append
@@ -7556,7 +7566,7 @@
        "                if (entryBytes > MAX_ZIP_ENTRY_BYTES || budget.expandedBytes > MAX_ZIP_TOTAL_BYTES) {"
        "                    throw IllegalStateException(\"ZIP expanded-byte limit exceeded\")"
        "                }"
-       "                val ratioBudget = maxOf(RATIO_FLOOR_BYTES, compressed.count * MAX_COMPRESSION_RATIO)"
+       "                val ratioBudget = compressionRatioBudget(RATIO_FLOOR_BYTES, compressed.count, MAX_COMPRESSION_RATIO)"
        "                if (budget.expandedBytes > ratioBudget) {"
        "                    throw IllegalStateException(\"ZIP compression-ratio limit exceeded\")"
        "                }"
@@ -7753,7 +7763,7 @@
        "                val remaining = MAX_ZIP_TOTAL_BYTES - budget.expandedBytes"
        "                if (remaining <= 0L) throw IllegalStateException(\"Import tree exceeds byte limit\")"
        "                val bytes = context.contentResolver.openInputStream(child.uri)?.use {"
-       "                    readBoundedBytes(it, minOf(MAX_ZIP_ENTRY_BYTES, remaining))"
+       "                    readBoundedBytes(it, boundedTreeReadLimit(MAX_ZIP_ENTRY_BYTES, remaining))"
        "                }"
        "                    ?: return@forEach"
        "                budget.expandedBytes += bytes.size.toLong()"