From d57b2d7c7475d36d707d4912ef4d0b748ba60604 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Sat, 11 Jul 2026 02:55:19 +0300 Subject: [PATCH 1/6] build: enforce the native (KLIB) public ABI in apiCheck binary-compatibility-validator's KLIB ABI validation is experimental and off by default, so apiCheck already compiled every native klib but then skipped the comparison -- the native public API was never actually gated. Enable it and commit the merged api/kuri.klib.api snapshot, so a source-compatible but ABI-breaking change to the native surface now fails the build alongside the existing JVM and Android ABI checks. --- kuri/api/kuri.klib.api | 555 +++++++++++++++++++++++++++++++++++++++++ kuri/build.gradle.kts | 16 +- 2 files changed, 570 insertions(+), 1 deletion(-) create mode 100644 kuri/api/kuri.klib.api diff --git a/kuri/api/kuri.klib.api b/kuri/api/kuri.klib.api new file mode 100644 index 0000000..792d805 --- /dev/null +++ b/kuri/api/kuri.klib.api @@ -0,0 +1,555 @@ +// Klib ABI Dump +// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxArm64, linuxX64, macosArm64, mingwX64, tvosArm64, tvosSimulatorArm64, wasmJs, watchosArm64, watchosSimulatorArm64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +final enum class org.dexpace.kuri.error/HostError : kotlin/Enum { // org.dexpace.kuri.error/HostError|null[0] + enum entry EmptyLabel // org.dexpace.kuri.error/HostError.EmptyLabel|null[0] + enum entry HostTooLong // org.dexpace.kuri.error/HostError.HostTooLong|null[0] + enum entry IdnaFailed // org.dexpace.kuri.error/HostError.IdnaFailed|null[0] + enum entry Ipv4NonNumeric // org.dexpace.kuri.error/HostError.Ipv4NonNumeric|null[0] + enum entry Ipv4Overflow // org.dexpace.kuri.error/HostError.Ipv4Overflow|null[0] + enum entry Ipv6Malformed // org.dexpace.kuri.error/HostError.Ipv6Malformed|null[0] + enum entry LabelTooLong // org.dexpace.kuri.error/HostError.LabelTooLong|null[0] + enum entry ZoneIdRejected // org.dexpace.kuri.error/HostError.ZoneIdRejected|null[0] + + final val entries // org.dexpace.kuri.error/HostError.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // org.dexpace.kuri.error/HostError.entries.|#static(){}[0] + + final fun valueOf(kotlin/String): org.dexpace.kuri.error/HostError // org.dexpace.kuri.error/HostError.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // org.dexpace.kuri.error/HostError.values|values#static(){}[0] +} + +final enum class org.dexpace.kuri.error/ValidationError : kotlin/Enum { // org.dexpace.kuri.error/ValidationError|null[0] + enum entry BACKSLASH_AS_SOLIDUS // org.dexpace.kuri.error/ValidationError.BACKSLASH_AS_SOLIDUS|null[0] + enum entry INVALID_URL_UNIT // org.dexpace.kuri.error/ValidationError.INVALID_URL_UNIT|null[0] + enum entry LEADING_OR_TRAILING_C0_CONTROL_OR_SPACE // org.dexpace.kuri.error/ValidationError.LEADING_OR_TRAILING_C0_CONTROL_OR_SPACE|null[0] + enum entry MISSING_AUTHORITY_SLASHES // org.dexpace.kuri.error/ValidationError.MISSING_AUTHORITY_SLASHES|null[0] + enum entry TAB_OR_NEWLINE_REMOVED // org.dexpace.kuri.error/ValidationError.TAB_OR_NEWLINE_REMOVED|null[0] + + final val entries // org.dexpace.kuri.error/ValidationError.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // org.dexpace.kuri.error/ValidationError.entries.|#static(){}[0] + + final fun valueOf(kotlin/String): org.dexpace.kuri.error/ValidationError // org.dexpace.kuri.error/ValidationError.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // org.dexpace.kuri.error/ValidationError.values|values#static(){}[0] +} + +sealed interface <#A: out kotlin/Any?> org.dexpace.kuri.error/ParseResult { // org.dexpace.kuri.error/ParseResult|null[0] + open fun getOrNull(): #A? // org.dexpace.kuri.error/ParseResult.getOrNull|getOrNull(){}[0] + open fun getOrThrow(): #A // org.dexpace.kuri.error/ParseResult.getOrThrow|getOrThrow(){}[0] + open fun isOk(): kotlin/Boolean // org.dexpace.kuri.error/ParseResult.isOk|isOk(){}[0] + + final class <#A1: out kotlin/Any?> Ok : org.dexpace.kuri.error/ParseResult<#A1> { // org.dexpace.kuri.error/ParseResult.Ok|null[0] + constructor (#A1) // org.dexpace.kuri.error/ParseResult.Ok.|(1:0){}[0] + + final val value // org.dexpace.kuri.error/ParseResult.Ok.value|{}value[0] + final fun (): #A1 // org.dexpace.kuri.error/ParseResult.Ok.value.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.error/ParseResult.Ok.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.error/ParseResult.Ok.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.error/ParseResult.Ok.toString|toString(){}[0] + } + + final class Err : org.dexpace.kuri.error/ParseResult { // org.dexpace.kuri.error/ParseResult.Err|null[0] + constructor (org.dexpace.kuri.error/UriParseError) // org.dexpace.kuri.error/ParseResult.Err.|(org.dexpace.kuri.error.UriParseError){}[0] + + final val error // org.dexpace.kuri.error/ParseResult.Err.error|{}error[0] + final fun (): org.dexpace.kuri.error/UriParseError // org.dexpace.kuri.error/ParseResult.Err.error.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.error/ParseResult.Err.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.error/ParseResult.Err.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.error/ParseResult.Err.toString|toString(){}[0] + } +} + +sealed interface org.dexpace.kuri.error/UriParseError { // org.dexpace.kuri.error/UriParseError|null[0] + open val message // org.dexpace.kuri.error/UriParseError.message|{}message[0] + open fun (): kotlin/String // org.dexpace.kuri.error/UriParseError.message.|(){}[0] + + final class ForbiddenHostCodePoint : org.dexpace.kuri.error/UriParseError { // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint|null[0] + constructor (kotlin/Int, kotlin/Int) // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint.|(kotlin.Int;kotlin.Int){}[0] + + final val at // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint.at|{}at[0] + final fun (): kotlin/Int // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint.at.|(){}[0] + final val codePoint // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint.codePoint|{}codePoint[0] + final fun (): kotlin/Int // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint.codePoint.|(){}[0] + + final fun component1(): kotlin/Int // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint.component1|component1(){}[0] + final fun component2(): kotlin/Int // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint.component2|component2(){}[0] + final fun copy(kotlin/Int = ..., kotlin/Int = ...): org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint.copy|copy(kotlin.Int;kotlin.Int){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.error/UriParseError.ForbiddenHostCodePoint.toString|toString(){}[0] + } + + final class InputTooLong : org.dexpace.kuri.error/UriParseError { // org.dexpace.kuri.error/UriParseError.InputTooLong|null[0] + constructor (kotlin/Int, kotlin/Int) // org.dexpace.kuri.error/UriParseError.InputTooLong.|(kotlin.Int;kotlin.Int){}[0] + + final val length // org.dexpace.kuri.error/UriParseError.InputTooLong.length|{}length[0] + final fun (): kotlin/Int // org.dexpace.kuri.error/UriParseError.InputTooLong.length.|(){}[0] + final val max // org.dexpace.kuri.error/UriParseError.InputTooLong.max|{}max[0] + final fun (): kotlin/Int // org.dexpace.kuri.error/UriParseError.InputTooLong.max.|(){}[0] + + final fun component1(): kotlin/Int // org.dexpace.kuri.error/UriParseError.InputTooLong.component1|component1(){}[0] + final fun component2(): kotlin/Int // org.dexpace.kuri.error/UriParseError.InputTooLong.component2|component2(){}[0] + final fun copy(kotlin/Int = ..., kotlin/Int = ...): org.dexpace.kuri.error/UriParseError.InputTooLong // org.dexpace.kuri.error/UriParseError.InputTooLong.copy|copy(kotlin.Int;kotlin.Int){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.error/UriParseError.InputTooLong.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.error/UriParseError.InputTooLong.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.error/UriParseError.InputTooLong.toString|toString(){}[0] + } + + final class InvalidHost : org.dexpace.kuri.error/UriParseError { // org.dexpace.kuri.error/UriParseError.InvalidHost|null[0] + constructor (kotlin/String, org.dexpace.kuri.error/HostError) // org.dexpace.kuri.error/UriParseError.InvalidHost.|(kotlin.String;org.dexpace.kuri.error.HostError){}[0] + + final val host // org.dexpace.kuri.error/UriParseError.InvalidHost.host|{}host[0] + final fun (): kotlin/String // org.dexpace.kuri.error/UriParseError.InvalidHost.host.|(){}[0] + final val reason // org.dexpace.kuri.error/UriParseError.InvalidHost.reason|{}reason[0] + final fun (): org.dexpace.kuri.error/HostError // org.dexpace.kuri.error/UriParseError.InvalidHost.reason.|(){}[0] + + final fun component1(): kotlin/String // org.dexpace.kuri.error/UriParseError.InvalidHost.component1|component1(){}[0] + final fun component2(): org.dexpace.kuri.error/HostError // org.dexpace.kuri.error/UriParseError.InvalidHost.component2|component2(){}[0] + final fun copy(kotlin/String = ..., org.dexpace.kuri.error/HostError = ...): org.dexpace.kuri.error/UriParseError.InvalidHost // org.dexpace.kuri.error/UriParseError.InvalidHost.copy|copy(kotlin.String;org.dexpace.kuri.error.HostError){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.error/UriParseError.InvalidHost.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.error/UriParseError.InvalidHost.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.error/UriParseError.InvalidHost.toString|toString(){}[0] + } + + final class InvalidPercentEncoding : org.dexpace.kuri.error/UriParseError { // org.dexpace.kuri.error/UriParseError.InvalidPercentEncoding|null[0] + constructor (kotlin/Int) // org.dexpace.kuri.error/UriParseError.InvalidPercentEncoding.|(kotlin.Int){}[0] + + final val at // org.dexpace.kuri.error/UriParseError.InvalidPercentEncoding.at|{}at[0] + final fun (): kotlin/Int // org.dexpace.kuri.error/UriParseError.InvalidPercentEncoding.at.|(){}[0] + + final fun component1(): kotlin/Int // org.dexpace.kuri.error/UriParseError.InvalidPercentEncoding.component1|component1(){}[0] + final fun copy(kotlin/Int = ...): org.dexpace.kuri.error/UriParseError.InvalidPercentEncoding // org.dexpace.kuri.error/UriParseError.InvalidPercentEncoding.copy|copy(kotlin.Int){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.error/UriParseError.InvalidPercentEncoding.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.error/UriParseError.InvalidPercentEncoding.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.error/UriParseError.InvalidPercentEncoding.toString|toString(){}[0] + } + + final class InvalidPort : org.dexpace.kuri.error/UriParseError { // org.dexpace.kuri.error/UriParseError.InvalidPort|null[0] + constructor (kotlin/String) // org.dexpace.kuri.error/UriParseError.InvalidPort.|(kotlin.String){}[0] + + final val text // org.dexpace.kuri.error/UriParseError.InvalidPort.text|{}text[0] + final fun (): kotlin/String // org.dexpace.kuri.error/UriParseError.InvalidPort.text.|(){}[0] + + final fun component1(): kotlin/String // org.dexpace.kuri.error/UriParseError.InvalidPort.component1|component1(){}[0] + final fun copy(kotlin/String = ...): org.dexpace.kuri.error/UriParseError.InvalidPort // org.dexpace.kuri.error/UriParseError.InvalidPort.copy|copy(kotlin.String){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.error/UriParseError.InvalidPort.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.error/UriParseError.InvalidPort.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.error/UriParseError.InvalidPort.toString|toString(){}[0] + } + + final class InvalidScheme : org.dexpace.kuri.error/UriParseError { // org.dexpace.kuri.error/UriParseError.InvalidScheme|null[0] + constructor (kotlin/Int, kotlin/String) // org.dexpace.kuri.error/UriParseError.InvalidScheme.|(kotlin.Int;kotlin.String){}[0] + + final val at // org.dexpace.kuri.error/UriParseError.InvalidScheme.at|{}at[0] + final fun (): kotlin/Int // org.dexpace.kuri.error/UriParseError.InvalidScheme.at.|(){}[0] + final val detail // org.dexpace.kuri.error/UriParseError.InvalidScheme.detail|{}detail[0] + final fun (): kotlin/String // org.dexpace.kuri.error/UriParseError.InvalidScheme.detail.|(){}[0] + + final fun component1(): kotlin/Int // org.dexpace.kuri.error/UriParseError.InvalidScheme.component1|component1(){}[0] + final fun component2(): kotlin/String // org.dexpace.kuri.error/UriParseError.InvalidScheme.component2|component2(){}[0] + final fun copy(kotlin/Int = ..., kotlin/String = ...): org.dexpace.kuri.error/UriParseError.InvalidScheme // org.dexpace.kuri.error/UriParseError.InvalidScheme.copy|copy(kotlin.Int;kotlin.String){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.error/UriParseError.InvalidScheme.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.error/UriParseError.InvalidScheme.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.error/UriParseError.InvalidScheme.toString|toString(){}[0] + } + + final object EmptyHost : org.dexpace.kuri.error/UriParseError { // org.dexpace.kuri.error/UriParseError.EmptyHost|null[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.error/UriParseError.EmptyHost.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.error/UriParseError.EmptyHost.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.error/UriParseError.EmptyHost.toString|toString(){}[0] + } + + final object MissingScheme : org.dexpace.kuri.error/UriParseError { // org.dexpace.kuri.error/UriParseError.MissingScheme|null[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.error/UriParseError.MissingScheme.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.error/UriParseError.MissingScheme.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.error/UriParseError.MissingScheme.toString|toString(){}[0] + } +} + +sealed interface org.dexpace.kuri.host/Host { // org.dexpace.kuri.host/Host|null[0] + open fun asText(): kotlin/String // org.dexpace.kuri.host/Host.asText|asText(){}[0] + + final class IpFuture : org.dexpace.kuri.host/Host { // org.dexpace.kuri.host/Host.IpFuture|null[0] + constructor (kotlin/String) // org.dexpace.kuri.host/Host.IpFuture.|(kotlin.String){}[0] + + final val value // org.dexpace.kuri.host/Host.IpFuture.value|{}value[0] + final fun (): kotlin/String // org.dexpace.kuri.host/Host.IpFuture.value.|(){}[0] + + final fun component1(): kotlin/String // org.dexpace.kuri.host/Host.IpFuture.component1|component1(){}[0] + final fun copy(kotlin/String = ...): org.dexpace.kuri.host/Host.IpFuture // org.dexpace.kuri.host/Host.IpFuture.copy|copy(kotlin.String){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.host/Host.IpFuture.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.host/Host.IpFuture.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.host/Host.IpFuture.toString|toString(){}[0] + } + + final class Ipv4 : org.dexpace.kuri.host/Host { // org.dexpace.kuri.host/Host.Ipv4|null[0] + constructor (kotlin/Int) // org.dexpace.kuri.host/Host.Ipv4.|(kotlin.Int){}[0] + + final val value // org.dexpace.kuri.host/Host.Ipv4.value|{}value[0] + final fun (): kotlin/Int // org.dexpace.kuri.host/Host.Ipv4.value.|(){}[0] + + final fun component1(): kotlin/Int // org.dexpace.kuri.host/Host.Ipv4.component1|component1(){}[0] + final fun copy(kotlin/Int = ...): org.dexpace.kuri.host/Host.Ipv4 // org.dexpace.kuri.host/Host.Ipv4.copy|copy(kotlin.Int){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.host/Host.Ipv4.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.host/Host.Ipv4.hashCode|hashCode(){}[0] + final fun octets(): kotlin/IntArray // org.dexpace.kuri.host/Host.Ipv4.octets|octets(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.host/Host.Ipv4.toString|toString(){}[0] + } + + final class Ipv6 : org.dexpace.kuri.host/Host { // org.dexpace.kuri.host/Host.Ipv6|null[0] + constructor (kotlin.collections/List, kotlin/String? = ...) // org.dexpace.kuri.host/Host.Ipv6.|(kotlin.collections.List;kotlin.String?){}[0] + + final val pieces // org.dexpace.kuri.host/Host.Ipv6.pieces|{}pieces[0] + final fun (): kotlin.collections/List // org.dexpace.kuri.host/Host.Ipv6.pieces.|(){}[0] + final val zoneId // org.dexpace.kuri.host/Host.Ipv6.zoneId|{}zoneId[0] + final fun (): kotlin/String? // org.dexpace.kuri.host/Host.Ipv6.zoneId.|(){}[0] + + final fun component1(): kotlin.collections/List // org.dexpace.kuri.host/Host.Ipv6.component1|component1(){}[0] + final fun component2(): kotlin/String? // org.dexpace.kuri.host/Host.Ipv6.component2|component2(){}[0] + final fun copy(kotlin.collections/List = ..., kotlin/String? = ...): org.dexpace.kuri.host/Host.Ipv6 // org.dexpace.kuri.host/Host.Ipv6.copy|copy(kotlin.collections.List;kotlin.String?){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.host/Host.Ipv6.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.host/Host.Ipv6.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.host/Host.Ipv6.toString|toString(){}[0] + } + + final class Opaque : org.dexpace.kuri.host/Host { // org.dexpace.kuri.host/Host.Opaque|null[0] + constructor (kotlin/String) // org.dexpace.kuri.host/Host.Opaque.|(kotlin.String){}[0] + + final val value // org.dexpace.kuri.host/Host.Opaque.value|{}value[0] + final fun (): kotlin/String // org.dexpace.kuri.host/Host.Opaque.value.|(){}[0] + + final fun component1(): kotlin/String // org.dexpace.kuri.host/Host.Opaque.component1|component1(){}[0] + final fun copy(kotlin/String = ...): org.dexpace.kuri.host/Host.Opaque // org.dexpace.kuri.host/Host.Opaque.copy|copy(kotlin.String){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.host/Host.Opaque.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.host/Host.Opaque.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.host/Host.Opaque.toString|toString(){}[0] + } + + final class RegName : org.dexpace.kuri.host/Host { // org.dexpace.kuri.host/Host.RegName|null[0] + constructor (kotlin/String) // org.dexpace.kuri.host/Host.RegName.|(kotlin.String){}[0] + + final val value // org.dexpace.kuri.host/Host.RegName.value|{}value[0] + final fun (): kotlin/String // org.dexpace.kuri.host/Host.RegName.value.|(){}[0] + + final fun component1(): kotlin/String // org.dexpace.kuri.host/Host.RegName.component1|component1(){}[0] + final fun copy(kotlin/String = ...): org.dexpace.kuri.host/Host.RegName // org.dexpace.kuri.host/Host.RegName.copy|copy(kotlin.String){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.host/Host.RegName.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.host/Host.RegName.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.host/Host.RegName.toString|toString(){}[0] + } + + final object Empty : org.dexpace.kuri.host/Host { // org.dexpace.kuri.host/Host.Empty|null[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.host/Host.Empty.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.host/Host.Empty.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.host/Host.Empty.toString|toString(){}[0] + } +} + +final class org.dexpace.kuri.error/UriSyntaxException : kotlin/IllegalArgumentException { // org.dexpace.kuri.error/UriSyntaxException|null[0] + final val error // org.dexpace.kuri.error/UriSyntaxException.error|{}error[0] + final fun (): org.dexpace.kuri.error/UriParseError // org.dexpace.kuri.error/UriSyntaxException.error.|(){}[0] +} + +final class org.dexpace.kuri.query/QueryParameter { // org.dexpace.kuri.query/QueryParameter|null[0] + constructor (kotlin/String, kotlin/String?) // org.dexpace.kuri.query/QueryParameter.|(kotlin.String;kotlin.String?){}[0] + + final val name // org.dexpace.kuri.query/QueryParameter.name|{}name[0] + final fun (): kotlin/String // org.dexpace.kuri.query/QueryParameter.name.|(){}[0] + final val value // org.dexpace.kuri.query/QueryParameter.value|{}value[0] + final fun (): kotlin/String? // org.dexpace.kuri.query/QueryParameter.value.|(){}[0] + + final fun component1(): kotlin/String // org.dexpace.kuri.query/QueryParameter.component1|component1(){}[0] + final fun component2(): kotlin/String? // org.dexpace.kuri.query/QueryParameter.component2|component2(){}[0] + final fun copy(kotlin/String = ..., kotlin/String? = ...): org.dexpace.kuri.query/QueryParameter // org.dexpace.kuri.query/QueryParameter.copy|copy(kotlin.String;kotlin.String?){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.query/QueryParameter.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.query/QueryParameter.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.query/QueryParameter.toString|toString(){}[0] +} + +final class org.dexpace.kuri.query/QueryParameters : kotlin.collections/Iterable { // org.dexpace.kuri.query/QueryParameters|null[0] + final val size // org.dexpace.kuri.query/QueryParameters.size|{}size[0] + final fun (): kotlin/Int // org.dexpace.kuri.query/QueryParameters.size.|(){}[0] + + final fun contains(kotlin/String): kotlin/Boolean // org.dexpace.kuri.query/QueryParameters.contains|contains(kotlin.String){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri.query/QueryParameters.equals|equals(kotlin.Any?){}[0] + final fun get(kotlin/String): kotlin/String? // org.dexpace.kuri.query/QueryParameters.get|get(kotlin.String){}[0] + final fun getAll(kotlin/String): kotlin.collections/List // org.dexpace.kuri.query/QueryParameters.getAll|getAll(kotlin.String){}[0] + final fun has(kotlin/String): kotlin/Boolean // org.dexpace.kuri.query/QueryParameters.has|has(kotlin.String){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri.query/QueryParameters.hashCode|hashCode(){}[0] + final fun isEmpty(): kotlin/Boolean // org.dexpace.kuri.query/QueryParameters.isEmpty|isEmpty(){}[0] + final fun iterator(): kotlin.collections/Iterator // org.dexpace.kuri.query/QueryParameters.iterator|iterator(){}[0] + final fun nameAt(kotlin/Int): kotlin/String // org.dexpace.kuri.query/QueryParameters.nameAt|nameAt(kotlin.Int){}[0] + final fun names(): kotlin.collections/Set // org.dexpace.kuri.query/QueryParameters.names|names(){}[0] + final fun newBuilder(): org.dexpace.kuri.query/QueryParametersBuilder // org.dexpace.kuri.query/QueryParameters.newBuilder|newBuilder(){}[0] + final fun split(kotlin/String, kotlin/Char): kotlin.collections/List // org.dexpace.kuri.query/QueryParameters.split|split(kotlin.String;kotlin.Char){}[0] + final fun toFormUrlEncoded(): kotlin/String // org.dexpace.kuri.query/QueryParameters.toFormUrlEncoded|toFormUrlEncoded(){}[0] + final fun toMap(): kotlin.collections/Map // org.dexpace.kuri.query/QueryParameters.toMap|toMap(){}[0] + final fun toQueryString(): kotlin/String // org.dexpace.kuri.query/QueryParameters.toQueryString|toQueryString(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri.query/QueryParameters.toString|toString(){}[0] + final fun valueAt(kotlin/Int): kotlin/String? // org.dexpace.kuri.query/QueryParameters.valueAt|valueAt(kotlin.Int){}[0] + + final object Companion { // org.dexpace.kuri.query/QueryParameters.Companion|null[0] + final fun of(kotlin.collections/Map): org.dexpace.kuri.query/QueryParameters // org.dexpace.kuri.query/QueryParameters.Companion.of|of(kotlin.collections.Map){}[0] + final fun of(kotlin/Array...): org.dexpace.kuri.query/QueryParameters // org.dexpace.kuri.query/QueryParameters.Companion.of|of(kotlin.Array...){}[0] + final fun parse(kotlin/String): org.dexpace.kuri.query/QueryParameters // org.dexpace.kuri.query/QueryParameters.Companion.parse|parse(kotlin.String){}[0] + final fun parseForm(kotlin/String): org.dexpace.kuri.query/QueryParameters // org.dexpace.kuri.query/QueryParameters.Companion.parseForm|parseForm(kotlin.String){}[0] + } +} + +final class org.dexpace.kuri.query/QueryParametersBuilder { // org.dexpace.kuri.query/QueryParametersBuilder|null[0] + constructor () // org.dexpace.kuri.query/QueryParametersBuilder.|(){}[0] + + final fun add(kotlin/String, kotlin/String?): org.dexpace.kuri.query/QueryParametersBuilder // org.dexpace.kuri.query/QueryParametersBuilder.add|add(kotlin.String;kotlin.String?){}[0] + final fun add(org.dexpace.kuri.query/QueryParameter): org.dexpace.kuri.query/QueryParametersBuilder // org.dexpace.kuri.query/QueryParametersBuilder.add|add(org.dexpace.kuri.query.QueryParameter){}[0] + final fun addAll(kotlin.collections/Map): org.dexpace.kuri.query/QueryParametersBuilder // org.dexpace.kuri.query/QueryParametersBuilder.addAll|addAll(kotlin.collections.Map){}[0] + final fun build(): org.dexpace.kuri.query/QueryParameters // org.dexpace.kuri.query/QueryParametersBuilder.build|build(){}[0] + final fun removeAll(kotlin/String): org.dexpace.kuri.query/QueryParametersBuilder // org.dexpace.kuri.query/QueryParametersBuilder.removeAll|removeAll(kotlin.String){}[0] + final fun set(kotlin/String, kotlin/String?): org.dexpace.kuri.query/QueryParametersBuilder // org.dexpace.kuri.query/QueryParametersBuilder.set|set(kotlin.String;kotlin.String?){}[0] + final fun set(org.dexpace.kuri.query/QueryParameter): org.dexpace.kuri.query/QueryParametersBuilder // org.dexpace.kuri.query/QueryParametersBuilder.set|set(org.dexpace.kuri.query.QueryParameter){}[0] + final fun sort(): org.dexpace.kuri.query/QueryParametersBuilder // org.dexpace.kuri.query/QueryParametersBuilder.sort|sort(){}[0] +} + +final class org.dexpace.kuri/ParseOptions { // org.dexpace.kuri/ParseOptions|null[0] + final val allowIpv6ZoneId // org.dexpace.kuri/ParseOptions.allowIpv6ZoneId|{}allowIpv6ZoneId[0] + final fun (): kotlin/Boolean // org.dexpace.kuri/ParseOptions.allowIpv6ZoneId.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri/ParseOptions.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri/ParseOptions.hashCode|hashCode(){}[0] + final fun newBuilder(): org.dexpace.kuri/ParseOptions.Builder // org.dexpace.kuri/ParseOptions.newBuilder|newBuilder(){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri/ParseOptions.toString|toString(){}[0] + + final class Builder { // org.dexpace.kuri/ParseOptions.Builder|null[0] + constructor () // org.dexpace.kuri/ParseOptions.Builder.|(){}[0] + + final fun allowIpv6ZoneId(kotlin/Boolean): org.dexpace.kuri/ParseOptions.Builder // org.dexpace.kuri/ParseOptions.Builder.allowIpv6ZoneId|allowIpv6ZoneId(kotlin.Boolean){}[0] + final fun build(): org.dexpace.kuri/ParseOptions // org.dexpace.kuri/ParseOptions.Builder.build|build(){}[0] + } + + final object Companion { // org.dexpace.kuri/ParseOptions.Companion|null[0] + final val DEFAULT // org.dexpace.kuri/ParseOptions.Companion.DEFAULT|{}DEFAULT[0] + final fun (): org.dexpace.kuri/ParseOptions // org.dexpace.kuri/ParseOptions.Companion.DEFAULT.|(){}[0] + } +} + +final class org.dexpace.kuri/Uri { // org.dexpace.kuri/Uri|null[0] + final val authority // org.dexpace.kuri/Uri.authority|{}authority[0] + final fun (): kotlin/String? // org.dexpace.kuri/Uri.authority.|(){}[0] + final val encodedPath // org.dexpace.kuri/Uri.encodedPath|{}encodedPath[0] + final fun (): kotlin/String // org.dexpace.kuri/Uri.encodedPath.|(){}[0] + final val fragment // org.dexpace.kuri/Uri.fragment|{}fragment[0] + final fun (): kotlin/String? // org.dexpace.kuri/Uri.fragment.|(){}[0] + final val host // org.dexpace.kuri/Uri.host|{}host[0] + final fun (): org.dexpace.kuri.host/Host? // org.dexpace.kuri/Uri.host.|(){}[0] + final val hostName // org.dexpace.kuri/Uri.hostName|{}hostName[0] + final fun (): kotlin/String? // org.dexpace.kuri/Uri.hostName.|(){}[0] + final val path // org.dexpace.kuri/Uri.path|{}path[0] + final fun (): kotlin/String // org.dexpace.kuri/Uri.path.|(){}[0] + final val pathSegments // org.dexpace.kuri/Uri.pathSegments|{}pathSegments[0] + final fun (): kotlin.collections/List // org.dexpace.kuri/Uri.pathSegments.|(){}[0] + final val port // org.dexpace.kuri/Uri.port|{}port[0] + final fun (): kotlin/Int? // org.dexpace.kuri/Uri.port.|(){}[0] + final val query // org.dexpace.kuri/Uri.query|{}query[0] + final fun (): kotlin/String? // org.dexpace.kuri/Uri.query.|(){}[0] + final val scheme // org.dexpace.kuri/Uri.scheme|{}scheme[0] + final fun (): kotlin/String? // org.dexpace.kuri/Uri.scheme.|(){}[0] + final val uriString // org.dexpace.kuri/Uri.uriString|{}uriString[0] + final fun (): kotlin/String // org.dexpace.kuri/Uri.uriString.|(){}[0] + final val userInfo // org.dexpace.kuri/Uri.userInfo|{}userInfo[0] + final fun (): kotlin/String? // org.dexpace.kuri/Uri.userInfo.|(){}[0] + + final fun effectivePort(): kotlin/Int? // org.dexpace.kuri/Uri.effectivePort|effectivePort(){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri/Uri.equals|equals(kotlin.Any?){}[0] + final fun fileExtension(): kotlin/String // org.dexpace.kuri/Uri.fileExtension|fileExtension(){}[0] + final fun fileName(): kotlin/String // org.dexpace.kuri/Uri.fileName|fileName(){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri/Uri.hashCode|hashCode(){}[0] + final fun isAbsolute(): kotlin/Boolean // org.dexpace.kuri/Uri.isAbsolute|isAbsolute(){}[0] + final fun isOpaquePath(): kotlin/Boolean // org.dexpace.kuri/Uri.isOpaquePath|isOpaquePath(){}[0] + final fun newBuilder(): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.newBuilder|newBuilder(){}[0] + final fun normalized(): org.dexpace.kuri/Uri // org.dexpace.kuri/Uri.normalized|normalized(){}[0] + final fun normalizedEquals(org.dexpace.kuri/Uri): kotlin/Boolean // org.dexpace.kuri/Uri.normalizedEquals|normalizedEquals(org.dexpace.kuri.Uri){}[0] + final fun queryParameters(): org.dexpace.kuri.query/QueryParameters // org.dexpace.kuri/Uri.queryParameters|queryParameters(){}[0] + final fun relativize(org.dexpace.kuri/Uri): org.dexpace.kuri/Uri? // org.dexpace.kuri/Uri.relativize|relativize(org.dexpace.kuri.Uri){}[0] + final fun resolve(kotlin/String, org.dexpace.kuri/ParseOptions = ...): org.dexpace.kuri.error/ParseResult // org.dexpace.kuri/Uri.resolve|resolve(kotlin.String;org.dexpace.kuri.ParseOptions){}[0] + final fun resolveOrNull(kotlin/String, org.dexpace.kuri/ParseOptions = ...): org.dexpace.kuri/Uri? // org.dexpace.kuri/Uri.resolveOrNull|resolveOrNull(kotlin.String;org.dexpace.kuri.ParseOptions){}[0] + final fun resolveOrThrow(kotlin/String, org.dexpace.kuri/ParseOptions = ...): org.dexpace.kuri/Uri // org.dexpace.kuri/Uri.resolveOrThrow|resolveOrThrow(kotlin.String;org.dexpace.kuri.ParseOptions){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri/Uri.toString|toString(){}[0] + final fun toUrl(): org.dexpace.kuri.error/ParseResult // org.dexpace.kuri/Uri.toUrl|toUrl(){}[0] + final fun toUrlOrNull(): org.dexpace.kuri/Url? // org.dexpace.kuri/Uri.toUrlOrNull|toUrlOrNull(){}[0] + final fun toUrlOrThrow(): org.dexpace.kuri/Url // org.dexpace.kuri/Uri.toUrlOrThrow|toUrlOrThrow(){}[0] + final fun withFragment(kotlin/String?): org.dexpace.kuri/Uri // org.dexpace.kuri/Uri.withFragment|withFragment(kotlin.String?){}[0] + final fun withPort(kotlin/Int?): org.dexpace.kuri/Uri // org.dexpace.kuri/Uri.withPort|withPort(kotlin.Int?){}[0] + final fun withoutFragment(): org.dexpace.kuri/Uri // org.dexpace.kuri/Uri.withoutFragment|withoutFragment(){}[0] + + final class Builder { // org.dexpace.kuri/Uri.Builder|null[0] + constructor () // org.dexpace.kuri/Uri.Builder.|(){}[0] + + final fun addEncodedPathSegment(kotlin/String): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.addEncodedPathSegment|addEncodedPathSegment(kotlin.String){}[0] + final fun addPathSegment(kotlin/String): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.addPathSegment|addPathSegment(kotlin.String){}[0] + final fun addPathSegments(kotlin/String): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.addPathSegments|addPathSegments(kotlin.String){}[0] + final fun addQueryParameter(kotlin/String, kotlin/String?): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.addQueryParameter|addQueryParameter(kotlin.String;kotlin.String?){}[0] + final fun allowIpv6ZoneId(kotlin/Boolean): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.allowIpv6ZoneId|allowIpv6ZoneId(kotlin.Boolean){}[0] + final fun build(): org.dexpace.kuri/Uri // org.dexpace.kuri/Uri.Builder.build|build(){}[0] + final fun buildOrNull(): org.dexpace.kuri/Uri? // org.dexpace.kuri/Uri.Builder.buildOrNull|buildOrNull(){}[0] + final fun encodedPath(kotlin/String): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.encodedPath|encodedPath(kotlin.String){}[0] + final fun fragment(kotlin/String?): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.fragment|fragment(kotlin.String?){}[0] + final fun host(kotlin/String?): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.host|host(kotlin.String?){}[0] + final fun host(org.dexpace.kuri.host/Host): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.host|host(org.dexpace.kuri.host.Host){}[0] + final fun port(kotlin/Int?): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.port|port(kotlin.Int?){}[0] + final fun query(kotlin/String?): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.query|query(kotlin.String?){}[0] + final fun removeAllQueryParameters(kotlin/String): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.removeAllQueryParameters|removeAllQueryParameters(kotlin.String){}[0] + final fun removePathSegment(kotlin/Int): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.removePathSegment|removePathSegment(kotlin.Int){}[0] + final fun scheme(kotlin/String?): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.scheme|scheme(kotlin.String?){}[0] + final fun setPathSegment(kotlin/Int, kotlin/String): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.setPathSegment|setPathSegment(kotlin.Int;kotlin.String){}[0] + final fun setQueryParameter(kotlin/String, kotlin/String?): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.setQueryParameter|setQueryParameter(kotlin.String;kotlin.String?){}[0] + final fun userInfo(kotlin/String?): org.dexpace.kuri/Uri.Builder // org.dexpace.kuri/Uri.Builder.userInfo|userInfo(kotlin.String?){}[0] + } + + final object Companion { // org.dexpace.kuri/Uri.Companion|null[0] + final fun canParse(kotlin/String, org.dexpace.kuri/ParseOptions = ...): kotlin/Boolean // org.dexpace.kuri/Uri.Companion.canParse|canParse(kotlin.String;org.dexpace.kuri.ParseOptions){}[0] + final fun parse(kotlin/String, org.dexpace.kuri/ParseOptions = ...): org.dexpace.kuri.error/ParseResult // org.dexpace.kuri/Uri.Companion.parse|parse(kotlin.String;org.dexpace.kuri.ParseOptions){}[0] + final fun parseOrNull(kotlin/String, org.dexpace.kuri/ParseOptions = ...): org.dexpace.kuri/Uri? // org.dexpace.kuri/Uri.Companion.parseOrNull|parseOrNull(kotlin.String;org.dexpace.kuri.ParseOptions){}[0] + final fun parseOrThrow(kotlin/String, org.dexpace.kuri/ParseOptions = ...): org.dexpace.kuri/Uri // org.dexpace.kuri/Uri.Companion.parseOrThrow|parseOrThrow(kotlin.String;org.dexpace.kuri.ParseOptions){}[0] + } +} + +final class org.dexpace.kuri/Url { // org.dexpace.kuri/Url|null[0] + final val authority // org.dexpace.kuri/Url.authority|{}authority[0] + final fun (): kotlin/String? // org.dexpace.kuri/Url.authority.|(){}[0] + final val effectivePort // org.dexpace.kuri/Url.effectivePort|{}effectivePort[0] + final fun (): kotlin/Int // org.dexpace.kuri/Url.effectivePort.|(){}[0] + final val encodedFragment // org.dexpace.kuri/Url.encodedFragment|{}encodedFragment[0] + final fun (): kotlin/String? // org.dexpace.kuri/Url.encodedFragment.|(){}[0] + final val encodedPath // org.dexpace.kuri/Url.encodedPath|{}encodedPath[0] + final fun (): kotlin/String // org.dexpace.kuri/Url.encodedPath.|(){}[0] + final val fragment // org.dexpace.kuri/Url.fragment|{}fragment[0] + final fun (): kotlin/String? // org.dexpace.kuri/Url.fragment.|(){}[0] + final val host // org.dexpace.kuri/Url.host|{}host[0] + final fun (): org.dexpace.kuri.host/Host? // org.dexpace.kuri/Url.host.|(){}[0] + final val hostName // org.dexpace.kuri/Url.hostName|{}hostName[0] + final fun (): kotlin/String? // org.dexpace.kuri/Url.hostName.|(){}[0] + final val href // org.dexpace.kuri/Url.href|{}href[0] + final fun (): kotlin/String // org.dexpace.kuri/Url.href.|(){}[0] + final val origin // org.dexpace.kuri/Url.origin|{}origin[0] + final fun (): kotlin/String // org.dexpace.kuri/Url.origin.|(){}[0] + final val password // org.dexpace.kuri/Url.password|{}password[0] + final fun (): kotlin/String // org.dexpace.kuri/Url.password.|(){}[0] + final val pathSegments // org.dexpace.kuri/Url.pathSegments|{}pathSegments[0] + final fun (): kotlin.collections/List // org.dexpace.kuri/Url.pathSegments.|(){}[0] + final val port // org.dexpace.kuri/Url.port|{}port[0] + final fun (): kotlin/Int? // org.dexpace.kuri/Url.port.|(){}[0] + final val query // org.dexpace.kuri/Url.query|{}query[0] + final fun (): kotlin/String? // org.dexpace.kuri/Url.query.|(){}[0] + final val queryParameters // org.dexpace.kuri/Url.queryParameters|{}queryParameters[0] + final fun (): org.dexpace.kuri.query/QueryParameters // org.dexpace.kuri/Url.queryParameters.|(){}[0] + final val scheme // org.dexpace.kuri/Url.scheme|{}scheme[0] + final fun (): kotlin/String // org.dexpace.kuri/Url.scheme.|(){}[0] + final val username // org.dexpace.kuri/Url.username|{}username[0] + final fun (): kotlin/String // org.dexpace.kuri/Url.username.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // org.dexpace.kuri/Url.equals|equals(kotlin.Any?){}[0] + final fun fileExtension(): kotlin/String // org.dexpace.kuri/Url.fileExtension|fileExtension(){}[0] + final fun fileName(): kotlin/String // org.dexpace.kuri/Url.fileName|fileName(){}[0] + final fun hasOpaqueOrigin(): kotlin/Boolean // org.dexpace.kuri/Url.hasOpaqueOrigin|hasOpaqueOrigin(){}[0] + final fun hashCode(): kotlin/Int // org.dexpace.kuri/Url.hashCode|hashCode(){}[0] + final fun isSpecial(): kotlin/Boolean // org.dexpace.kuri/Url.isSpecial|isSpecial(){}[0] + final fun newBuilder(): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.newBuilder|newBuilder(){}[0] + final fun relativize(org.dexpace.kuri/Url): kotlin/String? // org.dexpace.kuri/Url.relativize|relativize(org.dexpace.kuri.Url){}[0] + final fun resolve(kotlin/String): org.dexpace.kuri.error/ParseResult // org.dexpace.kuri/Url.resolve|resolve(kotlin.String){}[0] + final fun resolveOrNull(kotlin/String): org.dexpace.kuri/Url? // org.dexpace.kuri/Url.resolveOrNull|resolveOrNull(kotlin.String){}[0] + final fun resolveOrThrow(kotlin/String): org.dexpace.kuri/Url // org.dexpace.kuri/Url.resolveOrThrow|resolveOrThrow(kotlin.String){}[0] + final fun toString(): kotlin/String // org.dexpace.kuri/Url.toString|toString(){}[0] + final fun toUri(): org.dexpace.kuri/Uri // org.dexpace.kuri/Url.toUri|toUri(){}[0] + final fun validationErrors(): kotlin.collections/List // org.dexpace.kuri/Url.validationErrors|validationErrors(){}[0] + final fun withFragment(kotlin/String?): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withFragment|withFragment(kotlin.String?){}[0] + final fun withHash(kotlin/String): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withHash|withHash(kotlin.String){}[0] + final fun withHost(kotlin/String): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withHost|withHost(kotlin.String){}[0] + final fun withHostname(kotlin/String): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withHostname|withHostname(kotlin.String){}[0] + final fun withPassword(kotlin/String): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withPassword|withPassword(kotlin.String){}[0] + final fun withPathname(kotlin/String): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withPathname|withPathname(kotlin.String){}[0] + final fun withPort(kotlin/Int?): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withPort|withPort(kotlin.Int?){}[0] + final fun withPort(kotlin/String): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withPort|withPort(kotlin.String){}[0] + final fun withProtocol(kotlin/String): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withProtocol|withProtocol(kotlin.String){}[0] + final fun withSearch(kotlin/String): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withSearch|withSearch(kotlin.String){}[0] + final fun withUsername(kotlin/String): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withUsername|withUsername(kotlin.String){}[0] + final fun withoutFragment(): org.dexpace.kuri/Url // org.dexpace.kuri/Url.withoutFragment|withoutFragment(){}[0] + + final class Builder { // org.dexpace.kuri/Url.Builder|null[0] + constructor () // org.dexpace.kuri/Url.Builder.|(){}[0] + + final fun addEncodedPathSegment(kotlin/String): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.addEncodedPathSegment|addEncodedPathSegment(kotlin.String){}[0] + final fun addPathSegment(kotlin/String): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.addPathSegment|addPathSegment(kotlin.String){}[0] + final fun addPathSegments(kotlin/String): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.addPathSegments|addPathSegments(kotlin.String){}[0] + final fun addQueryParameter(kotlin/String, kotlin/String?): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.addQueryParameter|addQueryParameter(kotlin.String;kotlin.String?){}[0] + final fun build(): org.dexpace.kuri/Url // org.dexpace.kuri/Url.Builder.build|build(){}[0] + final fun buildOrNull(): org.dexpace.kuri/Url? // org.dexpace.kuri/Url.Builder.buildOrNull|buildOrNull(){}[0] + final fun encodedPath(kotlin/String): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.encodedPath|encodedPath(kotlin.String){}[0] + final fun fragment(kotlin/String?): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.fragment|fragment(kotlin.String?){}[0] + final fun host(kotlin/String): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.host|host(kotlin.String){}[0] + final fun host(org.dexpace.kuri.host/Host): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.host|host(org.dexpace.kuri.host.Host){}[0] + final fun password(kotlin/String): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.password|password(kotlin.String){}[0] + final fun port(kotlin/Int?): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.port|port(kotlin.Int?){}[0] + final fun query(kotlin/String?): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.query|query(kotlin.String?){}[0] + final fun removeAllQueryParameters(kotlin/String): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.removeAllQueryParameters|removeAllQueryParameters(kotlin.String){}[0] + final fun removePathSegment(kotlin/Int): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.removePathSegment|removePathSegment(kotlin.Int){}[0] + final fun scheme(kotlin/String): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.scheme|scheme(kotlin.String){}[0] + final fun setPathSegment(kotlin/Int, kotlin/String): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.setPathSegment|setPathSegment(kotlin.Int;kotlin.String){}[0] + final fun setQueryParameter(kotlin/String, kotlin/String?): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.setQueryParameter|setQueryParameter(kotlin.String;kotlin.String?){}[0] + final fun username(kotlin/String): org.dexpace.kuri/Url.Builder // org.dexpace.kuri/Url.Builder.username|username(kotlin.String){}[0] + } + + final object Companion { // org.dexpace.kuri/Url.Companion|null[0] + final fun canParse(kotlin/String, org.dexpace.kuri/Url? = ...): kotlin/Boolean // org.dexpace.kuri/Url.Companion.canParse|canParse(kotlin.String;org.dexpace.kuri.Url?){}[0] + final fun parse(kotlin/String, org.dexpace.kuri/Url? = ...): org.dexpace.kuri.error/ParseResult // org.dexpace.kuri/Url.Companion.parse|parse(kotlin.String;org.dexpace.kuri.Url?){}[0] + final fun parseOrNull(kotlin/String, org.dexpace.kuri/Url? = ...): org.dexpace.kuri/Url? // org.dexpace.kuri/Url.Companion.parseOrNull|parseOrNull(kotlin.String;org.dexpace.kuri.Url?){}[0] + final fun parseOrThrow(kotlin/String, org.dexpace.kuri/Url? = ...): org.dexpace.kuri/Url // org.dexpace.kuri/Url.Companion.parseOrThrow|parseOrThrow(kotlin.String;org.dexpace.kuri.Url?){}[0] + } +} + +final object org.dexpace.kuri.idna/Idn { // org.dexpace.kuri.idna/Idn|null[0] + final fun toAscii(kotlin/String): org.dexpace.kuri.error/ParseResult // org.dexpace.kuri.idna/Idn.toAscii|toAscii(kotlin.String){}[0] + final fun toUnicode(kotlin/String): kotlin/String // org.dexpace.kuri.idna/Idn.toUnicode|toUnicode(kotlin.String){}[0] +} + +final object org.dexpace.kuri.percent/Percent { // org.dexpace.kuri.percent/Percent|null[0] + final fun decode(kotlin/String): kotlin/String // org.dexpace.kuri.percent/Percent.decode|decode(kotlin.String){}[0] + final fun encode(kotlin/String, org.dexpace.kuri.percent/Percent.Component): kotlin/String // org.dexpace.kuri.percent/Percent.encode|encode(kotlin.String;org.dexpace.kuri.percent.Percent.Component){}[0] + + final enum class Component : kotlin/Enum { // org.dexpace.kuri.percent/Percent.Component|null[0] + enum entry COMPONENT // org.dexpace.kuri.percent/Percent.Component.COMPONENT|null[0] + enum entry FRAGMENT // org.dexpace.kuri.percent/Percent.Component.FRAGMENT|null[0] + enum entry PATH_SEGMENT // org.dexpace.kuri.percent/Percent.Component.PATH_SEGMENT|null[0] + enum entry QUERY // org.dexpace.kuri.percent/Percent.Component.QUERY|null[0] + enum entry USER_INFO // org.dexpace.kuri.percent/Percent.Component.USER_INFO|null[0] + + final val entries // org.dexpace.kuri.percent/Percent.Component.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // org.dexpace.kuri.percent/Percent.Component.entries.|#static(){}[0] + + final fun valueOf(kotlin/String): org.dexpace.kuri.percent/Percent.Component // org.dexpace.kuri.percent/Percent.Component.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // org.dexpace.kuri.percent/Percent.Component.values|values#static(){}[0] + } +} + +final object org.dexpace.kuri.scheme/Schemes { // org.dexpace.kuri.scheme/Schemes|null[0] + final fun defaultPort(kotlin/String): kotlin/Int? // org.dexpace.kuri.scheme/Schemes.defaultPort|defaultPort(kotlin.String){}[0] + final fun isSpecial(kotlin/String): kotlin/Boolean // org.dexpace.kuri.scheme/Schemes.isSpecial|isSpecial(kotlin.String){}[0] + final fun isValid(kotlin/String): kotlin/Boolean // org.dexpace.kuri.scheme/Schemes.isValid|isValid(kotlin.String){}[0] +} + +final object org.dexpace.kuri/Iri { // org.dexpace.kuri/Iri|null[0] + final fun toUnicode(org.dexpace.kuri/Uri): kotlin/String // org.dexpace.kuri/Iri.toUnicode|toUnicode(org.dexpace.kuri.Uri){}[0] + final fun toUri(kotlin/String): org.dexpace.kuri.error/ParseResult // org.dexpace.kuri/Iri.toUri|toUri(kotlin.String){}[0] +} + +final object org.dexpace.kuri/Kuri { // org.dexpace.kuri/Kuri|null[0] + final const val VERSION // org.dexpace.kuri/Kuri.VERSION|{}VERSION[0] + final fun (): kotlin/String // org.dexpace.kuri/Kuri.VERSION.|(){}[0] +} + +final fun <#A: kotlin/Any?, #B: kotlin/Any?> (org.dexpace.kuri.error/ParseResult<#A>).org.dexpace.kuri.error/map(kotlin/Function1<#A, #B>): org.dexpace.kuri.error/ParseResult<#B> // org.dexpace.kuri.error/map|map@org.dexpace.kuri.error.ParseResult<0:0>(kotlin.Function1<0:0,0:1>){0§;1§}[0] +final inline fun <#A: kotlin/Any?, #B: kotlin/Any?> (org.dexpace.kuri.error/ParseResult<#A>).org.dexpace.kuri.error/fold(kotlin/Function1<#A, #B>, kotlin/Function1): #B // org.dexpace.kuri.error/fold|fold@org.dexpace.kuri.error.ParseResult<0:0>(kotlin.Function1<0:0,0:1>;kotlin.Function1){0§;1§}[0] diff --git a/kuri/build.gradle.kts b/kuri/build.gradle.kts index a053e39..1d7e7b2 100644 --- a/kuri/build.gradle.kts +++ b/kuri/build.gradle.kts @@ -1,4 +1,7 @@ -@file:OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class) +@file:OptIn( + org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class, + kotlinx.validation.ExperimentalBCVApi::class, +) import com.vanniktech.maven.publish.JavadocJar import com.vanniktech.maven.publish.KotlinMultiplatform @@ -221,6 +224,17 @@ kover { } } +// Enforce the native (KLIB) public ABI, not only the JVM/Android one. binary-compatibility-validator's +// klib validation is experimental and off by default, so `apiCheck` was already compiling every native +// klib but then skipping the comparison — wasted work that guaranteed nothing. Enabling it makes +// `apiDump` emit the merged `api/kuri.klib.api` snapshot and `apiCheck` diff each native target's ABI +// against it, so a source-compatible but ABI-breaking change to the native surface fails the build. +apiValidation { + klib { + enabled = true + } +} + // Publish every Kotlin Multiplatform target (plus the root `kotlinMultiplatform` publication) to Maven // Central through the Central Portal. The vanniktech plugin wires the per-target publications, a // Dokka-generated Javadoc jar and GPG signing; coordinates derive from the module `group` and the From 9305aa8db3ae48b7bb86ad4fab4f4554d424b480 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Sat, 11 Jul 2026 02:55:19 +0300 Subject: [PATCH 2/6] ci: cache the Kotlin/Native toolchain and Android AVD The JVM quality-gate and native matrix jobs re-downloaded the ~1 GB Kotlin/Native toolchain (compiler + LLVM/sysroot) on every run, because setup-gradle caches ~/.gradle but not ~/.konan; the emulator job recreated its AVD and re-downloaded the system image each time; and the Android unit job re-downloaded the command-line tools despite a preinstalled SDK. Cache ~/.konan and the AVD snapshot, restoring on every branch but writing only from main so pull requests stay fast without evicting main's cache. Drop android-actions/setup-android from the host-unit job (the runner's preinstalled SDK covers compileSdk 35) and silence the disabled-native-target warnings that cluttered every job's log. --- .github/workflows/ci.yml | 83 ++++++++++++++++++++++++++++++++++++++-- gradle.properties | 4 ++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31eff44..4d8be8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,9 +52,30 @@ jobs: distribution: corretto java-version: '21' - uses: gradle/actions/setup-gradle@v4 + # Restore the Kotlin/Native toolchain (~1 GB: compiler bundle + LLVM/sysroot/gcc deps under + # ~/.konan). setup-gradle caches ~/.gradle but not ~/.konan, and apiCheck's KLIB ABI validation + # compiles every native klib here, so without this the whole bundle re-downloads each run. + # Restore runs on every branch (PRs reuse main's cache); only main writes it (see the save step + # below). The key tracks the version catalog; restore-keys give a partial hit when it changes. + - name: Restore Kotlin/Native toolchain + id: konan-cache + uses: actions/cache/restore@v4 + with: + path: ~/.konan + key: ${{ runner.os }}-konan-${{ hashFiles('gradle/libs.versions.toml') }} + restore-keys: ${{ runner.os }}-konan- - name: ktlint, detekt, binary-compatibility and JVM coverage floor # koverVerify pulls in jvmTest and enforces the 80% line-coverage rule. run: ./gradlew ktlintCheck detekt apiCheck jvmTest koverVerify --stacktrace + # Write the toolchain cache only from main, and only when the restore was not an exact hit + # (GitHub caches are write-once per key). PRs never write, so parallel branches can't evict + # main's warm cache. + - name: Save Kotlin/Native toolchain + if: github.ref == 'refs/heads/main' && steps.konan-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: ~/.konan + key: ${{ runner.os }}-konan-${{ hashFiles('gradle/libs.versions.toml') }} web: name: JS · Wasm tests (Linux x64) @@ -117,8 +138,26 @@ jobs: distribution: corretto java-version: '21' - uses: gradle/actions/setup-gradle@v4 + # Restore the Kotlin/Native toolchain (~1 GB under ~/.konan) that this native job compiles and + # links against; setup-gradle caches ~/.gradle but not ~/.konan. runner.os in the key keeps the + # Linux/Windows/macOS legs separate. Restore runs on every branch; only main writes it (below). + - name: Restore Kotlin/Native toolchain + id: konan-cache + uses: actions/cache/restore@v4 + with: + path: ~/.konan + key: ${{ runner.os }}-konan-${{ hashFiles('gradle/libs.versions.toml') }} + restore-keys: ${{ runner.os }}-konan- - name: ${{ matrix.name }} run: ./gradlew ${{ matrix.tasks }} --stacktrace + # Write the toolchain cache only from main, and only on a non-exact restore (caches are + # write-once per key), so PR legs never evict main's warm per-OS cache. + - name: Save Kotlin/Native toolchain + if: github.ref == 'refs/heads/main' && steps.konan-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: ~/.konan + key: ${{ runner.os }}-konan-${{ hashFiles('gradle/libs.versions.toml') }} android-unit: name: Android unit tests (Linux x64) @@ -131,8 +170,6 @@ jobs: with: distribution: corretto java-version: '21' - # AGP needs an Android SDK at configuration time; this installs it and accepts licenses. - - uses: android-actions/setup-android@v3 - uses: gradle/actions/setup-gradle@v4 - name: Android host (JVM) unit tests # Runs commonTest compiled for the Android target on the JVM — no emulator. @@ -157,6 +194,35 @@ jobs: echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm + # Restore the cached AVD (system image, created device, boot snapshot) under ~/.android so a run + # can skip the image download, AVD creation and cold boot and load a warm snapshot instead. + # Restore runs on every branch (PRs reuse main's snapshot); only main writes it (see save below). + # The key pins every coordinate that affects the snapshot; bump the -v suffix if the emulator + # options change, since GitHub caches are write-once per key. + - name: Restore AVD snapshot + id: avd-cache + uses: actions/cache/restore@v4 + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-30-aosp_atd-x86_64-pixel6-v1 + # On main with no cached snapshot, boot once to create the AVD and write a clean boot snapshot + # for the cache. Emulator options match the test run so the snapshot stays valid. Gated to main + # because only main persists it — on PRs the test step below just cold-boots instead of booting + # twice. + - name: Create AVD and generate snapshot for caching + if: github.ref == 'refs/heads/main' && steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 30 + target: aosp_atd + arch: x86_64 + profile: pixel_6 + force-avd-creation: false + disable-animations: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + script: echo "Generated AVD snapshot for caching." - name: Android instrumented tests on an emulator uses: reactivecircus/android-emulator-runner@v2 with: @@ -164,10 +230,21 @@ jobs: target: aosp_atd arch: x86_64 profile: pixel_6 + force-avd-creation: false disable-animations: true - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + # -no-snapshot-save: boot from the cached snapshot but don't rewrite it on exit. + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -no-snapshot-save -camera-back none # Installs and runs the androidDeviceTest instrumentation (an ART smoke test) on the emulator. script: ./gradlew :kuri:connectedAndroidDeviceTest --stacktrace + # Persist the AVD snapshot for later runs — only from main, and only on a non-exact restore. + - name: Save AVD snapshot + if: github.ref == 'refs/heads/main' && steps.avd-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-30-aosp_atd-x86_64-pixel6-v1 # Aggregate gate: a single check to require in branch protection. Green only when every # job above succeeded; fails if any was skipped, cancelled or failed. diff --git a/gradle.properties b/gradle.properties index ae8b356..559e99d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,6 +3,10 @@ org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 org.gradle.caching=true org.gradle.configuration-cache=true kotlin.mpp.stability.nowarn=true +# Native tasks for targets that can't run on the current CI host (e.g. iosSimulator on Linux) are +# disabled by Kotlin and emit a warning each; this silences that wall of warnings without changing +# what builds. Cross-compiled klib/*TestBinaries build breaks are still caught by the native jobs. +kotlin.native.ignoreDisabledTargets=true android.useAndroidX=true # Dokka 2.x runs in V1 (deprecated) mode unless the V2 plugin is opted into; V2 provides the From d7e734aef6564893c2cd11af5f1420c000059360 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Sat, 11 Jul 2026 02:55:19 +0300 Subject: [PATCH 3/6] ci: add CodeQL analysis for the Java/Kotlin surface Scan the JVM (Java + Kotlin) compilation with CodeQL on pushes to main, pull requests, and a weekly schedule, using the security-and-quality query suite so it runs the full security (SAST) query set plus CodeQL's reliability and maintainability queries. In this KMP project all production code is common + jvm Kotlin, so a manual traced build of the JVM target (compileKotlinJvm) covers the whole analysable surface -- autobuild is avoided because CodeQL cannot extract the native/JS/Wasm targets. The machine-generated Unicode lookup tables are excluded from results to keep the alert list focused on hand-written code. --- .github/codeql/codeql-config.yml | 17 ++++++++ .github/workflows/codeql.yml | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 .github/codeql/codeql-config.yml create mode 100644 .github/workflows/codeql.yml diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000..4da3fd5 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,17 @@ +name: kuri CodeQL config + +# SAST-first. security-and-quality is a superset of security-extended, so this runs CodeQL's full +# security query set (injection / SSRF / path-traversal / unsafe-parsing / crypto / etc. — the ones +# that matter for a URL/URI library that turns untrusted text into hosts, paths and queries) plus +# its reliability and maintainability queries. Security is the goal; the quality queries are the +# "whatever else it provides". +queries: + - uses: security-and-quality + +# The IDNA/UTS-46 mapping + validity tables and the NFC table are machine-generated from the Unicode +# UCD (thousands of entries, regenerated by the codegen tasks, never hand-edited). Findings in them +# are noise, not defects, so keep them out of the alert list and focus scanning on hand-written code. +paths-ignore: + - '**/idna/IdnaMappingTableData.kt' + - '**/idna/IdnaValidityData.kt' + - '**/idna/NfcData.kt' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..312ee7c --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,66 @@ +name: CodeQL + +# CodeQL SAST (static application security testing) for the JVM (Java + Kotlin) surface of kuri, +# plus CodeQL's reliability/maintainability queries — the query suite is set to security-and-quality +# in .github/codeql/codeql-config.yml. CodeQL only extracts JVM-bytecode-producing compilation; in +# this KMP project all production code is common + jvm Kotlin (the whole parsing engine lives in +# commonMain, and the reflective binder in kuri-bind's jvmMain), which the JVM target compiles, so a +# single traced JVM build covers the entire analysable surface. The native/JS/Wasm targets are +# outside CodeQL's reach and are covered by the CI test matrix instead. + +on: + push: + branches: [ main ] + pull_request: + schedule: + # Weekly re-scan (Mondays 03:19 UTC) so newly published CodeQL queries flag issues even on a + # week with no code change. Off-the-hour to avoid the top-of-hour scheduling congestion. + - cron: '19 3 * * 1' + +# Least privilege by default; the analyze job widens to what code-scanning upload needs. +permissions: + contents: read + +jobs: + analyze: + name: Analyze (Java/Kotlin) + runs-on: ubuntu-latest + # A wedged extractor or build should fail fast rather than burn the 6-hour default. + timeout-minutes: 30 + permissions: + # Upload the SARIF results to the repository's code-scanning alerts. + security-events: write + # Read workflow run metadata (required by CodeQL on private repos; harmless here). + actions: read + contents: read + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + # Match the CI jobs: Gradle runs on Corretto 21 (the module's jvmToolchain(21)). + distribution: corretto + java-version: '21' + - uses: gradle/actions/setup-gradle@v4 + + # Initialise the CodeQL tracer BEFORE the build so the Kotlin/Java compilation is extracted. + # 'java' is CodeQL's identifier for the Java/Kotlin extractor (it analyses Kotlin too). + - name: Initialize CodeQL + uses: github/codeql-action/init@v2.26.0 + with: + languages: java + # security-and-quality query suite + generated-code exclusions. + config-file: ./.github/codeql/codeql-config.yml + + # Manual traced build instead of autobuild: compile only the JVM target of every module + # (compileKotlinJvm runs in :kuri and :kuri-bind). Autobuild would run the whole KMP build, + # including the native/JS/Wasm targets CodeQL can't extract. --no-build-cache forces a real + # compile (the project has the Gradle build cache on, which would otherwise serve cached + # classes and leave the tracer nothing to see); --no-daemon keeps compilation in the traced + # build process rather than a pre-existing untraced daemon. + - name: Compile JVM sources for CodeQL + run: ./gradlew compileKotlinJvm --no-daemon --no-build-cache --stacktrace + + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@v2.26.0 + with: + category: "/language:java" From ea4d194e9e4f96d81e607d8d96d6dd1468a6de1b Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Sat, 11 Jul 2026 03:40:46 +0300 Subject: [PATCH 4/6] ci: move CodeQL to codeql-action v4 and scope the Konan cache per job Follow-ups to the CI hardening in this branch: - CodeQL: bump github/codeql-action from @v2.26.0 to @v4 on both init and analyze. 2.26.0 is the CodeQL CLI bundle version (codeql-bundle-v2.26.0), not an action version; @v2.26.0 pinned the v2 action major, retired in January 2025. @v4 is the current major (v3 deprecates December 2026) and ships the 2.26.x CLI bundle itself. Declare build-mode: manual so the traced compileKotlinJvm build is the explicit database source and autobuild (which would build the whole KMP project) is never used. - CodeQL: add a concurrency group so a new push to a PR cancels its in-flight scan while main runs still finish, and note in the header that platform actuals (js/native/wasm) fall outside the JVM extraction. - Scope the ~/.konan cache keys per job (-gate- for the quality gate, -native- for the native matrix) so the two Linux jobs no longer race to write one shared key on main and each caches the toolchain subset it pulls. - Reword the AVD snapshot comment: PRs warm-boot from main's cached snapshot (a single boot), they do not cold-boot. --- .github/workflows/ci.yml | 21 ++++++++++++--------- .github/workflows/codeql.yml | 25 +++++++++++++++++++++---- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d8be8f..a09a1bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,6 +55,8 @@ jobs: # Restore the Kotlin/Native toolchain (~1 GB: compiler bundle + LLVM/sysroot/gcc deps under # ~/.konan). setup-gradle caches ~/.gradle but not ~/.konan, and apiCheck's KLIB ABI validation # compiles every native klib here, so without this the whole bundle re-downloads each run. + # The key is scoped `-gate-` (the native job below uses `-native-`) so the two Linux jobs each + # cache the toolchain subset they actually pull and never race to write one shared key on main. # Restore runs on every branch (PRs reuse main's cache); only main writes it (see the save step # below). The key tracks the version catalog; restore-keys give a partial hit when it changes. - name: Restore Kotlin/Native toolchain @@ -62,8 +64,8 @@ jobs: uses: actions/cache/restore@v4 with: path: ~/.konan - key: ${{ runner.os }}-konan-${{ hashFiles('gradle/libs.versions.toml') }} - restore-keys: ${{ runner.os }}-konan- + key: ${{ runner.os }}-konan-gate-${{ hashFiles('gradle/libs.versions.toml') }} + restore-keys: ${{ runner.os }}-konan-gate- - name: ktlint, detekt, binary-compatibility and JVM coverage floor # koverVerify pulls in jvmTest and enforces the 80% line-coverage rule. run: ./gradlew ktlintCheck detekt apiCheck jvmTest koverVerify --stacktrace @@ -75,7 +77,7 @@ jobs: uses: actions/cache/save@v4 with: path: ~/.konan - key: ${{ runner.os }}-konan-${{ hashFiles('gradle/libs.versions.toml') }} + key: ${{ runner.os }}-konan-gate-${{ hashFiles('gradle/libs.versions.toml') }} web: name: JS · Wasm tests (Linux x64) @@ -140,14 +142,15 @@ jobs: - uses: gradle/actions/setup-gradle@v4 # Restore the Kotlin/Native toolchain (~1 GB under ~/.konan) that this native job compiles and # links against; setup-gradle caches ~/.gradle but not ~/.konan. runner.os in the key keeps the - # Linux/Windows/macOS legs separate. Restore runs on every branch; only main writes it (below). + # Linux/Windows/macOS legs separate, and the `-native-` scope keeps the Linux leg from colliding + # with the quality-gate job's `-gate-` cache. Restore runs on every branch; only main writes it. - name: Restore Kotlin/Native toolchain id: konan-cache uses: actions/cache/restore@v4 with: path: ~/.konan - key: ${{ runner.os }}-konan-${{ hashFiles('gradle/libs.versions.toml') }} - restore-keys: ${{ runner.os }}-konan- + key: ${{ runner.os }}-konan-native-${{ hashFiles('gradle/libs.versions.toml') }} + restore-keys: ${{ runner.os }}-konan-native- - name: ${{ matrix.name }} run: ./gradlew ${{ matrix.tasks }} --stacktrace # Write the toolchain cache only from main, and only on a non-exact restore (caches are @@ -157,7 +160,7 @@ jobs: uses: actions/cache/save@v4 with: path: ~/.konan - key: ${{ runner.os }}-konan-${{ hashFiles('gradle/libs.versions.toml') }} + key: ${{ runner.os }}-konan-native-${{ hashFiles('gradle/libs.versions.toml') }} android-unit: name: Android unit tests (Linux x64) @@ -209,8 +212,8 @@ jobs: key: avd-30-aosp_atd-x86_64-pixel6-v1 # On main with no cached snapshot, boot once to create the AVD and write a clean boot snapshot # for the cache. Emulator options match the test run so the snapshot stays valid. Gated to main - # because only main persists it — on PRs the test step below just cold-boots instead of booting - # twice. + # because only main persists it — PRs skip this create-and-snapshot step and run the test step + # below once, warm-booting from main's cached snapshot when present (a single boot, not two). - name: Create AVD and generate snapshot for caching if: github.ref == 'refs/heads/main' && steps.avd-cache.outputs.cache-hit != 'true' uses: reactivecircus/android-emulator-runner@v2 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 312ee7c..ad8baf2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -5,8 +5,9 @@ name: CodeQL # in .github/codeql/codeql-config.yml. CodeQL only extracts JVM-bytecode-producing compilation; in # this KMP project all production code is common + jvm Kotlin (the whole parsing engine lives in # commonMain, and the reflective binder in kuri-bind's jvmMain), which the JVM target compiles, so a -# single traced JVM build covers the entire analysable surface. The native/JS/Wasm targets are -# outside CodeQL's reach and are covered by the CI test matrix instead. +# single traced JVM build covers commonMain + jvmMain — the entire security-relevant surface. Only +# platform-specific `actual` bodies (in jsMain/nativeMain/wasmJsMain, if any) fall outside this JVM +# extraction; CodeQL can't extract those targets, and they are covered by the CI test matrix instead. on: push: @@ -17,6 +18,12 @@ on: # week with no code change. Off-the-hour to avoid the top-of-hour scheduling congestion. - cron: '19 3 * * 1' +# A newer push to the same PR ref cancels an in-flight scan instead of stacking concurrent analyses; +# main runs are never cancelled, so every default-branch commit keeps its own recorded CodeQL result. +concurrency: + group: codeql-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + # Least privilege by default; the analyze job widens to what code-scanning upload needs. permissions: contents: read @@ -44,10 +51,20 @@ jobs: # Initialise the CodeQL tracer BEFORE the build so the Kotlin/Java compilation is extracted. # 'java' is CodeQL's identifier for the Java/Kotlin extractor (it analyses Kotlin too). + # Pinned to the codeql-action's current major (@v4 → latest 4.x), matching how every other + # action here is pinned by major tag. The action version and the CodeQL CLI *bundle* version are + # separate schemes: @v4 selects the action, which ships a recent CLI bundle (2.26.x) on its own. + # (The previous @v2.26.0 pinned the long-deprecated v2 *action* — not the 2.26.0 CLI bundle it + # was mistaken for.) - name: Initialize CodeQL - uses: github/codeql-action/init@v2.26.0 + uses: github/codeql-action/init@v4 with: languages: java + # v4's explicit mode for a compiled language whose database is built by manual commands + # between init and analyze (the `compileKotlinJvm` step below), never by autobuild — which + # would build the whole KMP project. Omitting it works too (the tracer path is identical for + # Java), but declaring it self-documents that autobuild is deliberately avoided. + build-mode: manual # security-and-quality query suite + generated-code exclusions. config-file: ./.github/codeql/codeql-config.yml @@ -61,6 +78,6 @@ jobs: run: ./gradlew compileKotlinJvm --no-daemon --no-build-cache --stacktrace - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@v2.26.0 + uses: github/codeql-action/analyze@v4 with: category: "/language:java" From 6715ac912d839e8f73fcb335037c4bf1b3dcba93 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 13 Jul 2026 12:10:38 +0300 Subject: [PATCH 5/6] ci: pin the emulator action and sharpen the native-ABI/cache notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pin reactivecircus/android-emulator-runner to a commit SHA (v2.38.0) on both uses so a moved tag can't change what runs during emulator boot; GitHub-owned actions stay on major tags. Correct the CI comments to match what the build actually does: the two Linux Konan caches are scoped separately because the quality-gate job cross-builds the mingw klib for the ABI dump while the native leg does not, so they populate different ~/.konan subsets; and the AVD cache holds the created device and boot snapshot only, not the system-image package (sdkmanager still installs that). Document that KLIB ABI enforcement compares only the targets buildable on the apiCheck host — on the Linux gate the Apple targets are inferred from the merged dump, not re-verified — and note in CLAUDE.md that the kuri.klib.api dump must be regenerated on macOS or the Apple targets are silently dropped. Drop the redundant literal prefix from the CodeQL concurrency group. --- .github/workflows/ci.yml | 27 +++++++++++++++++---------- .github/workflows/codeql.yml | 2 +- CLAUDE.md | 5 ++++- kuri/build.gradle.kts | 5 +++++ 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a09a1bb..166b99e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,10 +55,13 @@ jobs: # Restore the Kotlin/Native toolchain (~1 GB: compiler bundle + LLVM/sysroot/gcc deps under # ~/.konan). setup-gradle caches ~/.gradle but not ~/.konan, and apiCheck's KLIB ABI validation # compiles every native klib here, so without this the whole bundle re-downloads each run. - # The key is scoped `-gate-` (the native job below uses `-native-`) so the two Linux jobs each - # cache the toolchain subset they actually pull and never race to write one shared key on main. - # Restore runs on every branch (PRs reuse main's cache); only main writes it (see the save step - # below). The key tracks the version catalog; restore-keys give a partial hit when it changes. + # The key is scoped `-gate-` (the native job below uses `-native-`) because the two Linux jobs + # populate different ~/.konan subsets: this job's apiCheck cross-builds the mingw klib for the ABI + # dump, which the `-native-` leg (linuxX64/linuxArm64 only) never pulls. Separate keys let each job + # cache exactly its own subset instead of a shared write-once key locking the cache to whichever + # subset landed first. Restore runs on every branch (PRs reuse main's cache); only main writes it + # (see the save step below). The key tracks the version catalog; restore-keys give a partial hit + # when it changes. - name: Restore Kotlin/Native toolchain id: konan-cache uses: actions/cache/restore@v4 @@ -142,8 +145,10 @@ jobs: - uses: gradle/actions/setup-gradle@v4 # Restore the Kotlin/Native toolchain (~1 GB under ~/.konan) that this native job compiles and # links against; setup-gradle caches ~/.gradle but not ~/.konan. runner.os in the key keeps the - # Linux/Windows/macOS legs separate, and the `-native-` scope keeps the Linux leg from colliding - # with the quality-gate job's `-gate-` cache. Restore runs on every branch; only main writes it. + # Linux/Windows/macOS legs separate, and the `-native-` scope keeps the Linux leg's smaller + # toolchain subset (linuxX64/linuxArm64) from sharing a key with the quality-gate job's broader + # `-gate-` cache, which also cross-builds the mingw klib for the ABI dump. Restore runs on every + # branch; only main writes it. - name: Restore Kotlin/Native toolchain id: konan-cache uses: actions/cache/restore@v4 @@ -197,8 +202,10 @@ jobs: echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - # Restore the cached AVD (system image, created device, boot snapshot) under ~/.android so a run - # can skip the image download, AVD creation and cold boot and load a warm snapshot instead. + # Restore the cached AVD (the created device plus its boot snapshot) under ~/.android so a run can + # skip AVD creation and the cold boot and load a warm snapshot instead. This does not cache the + # system-image package itself — sdkmanager installs that under $ANDROID_SDK_ROOT/system-images, + # outside ~/.android — so the emulator action still ensures the image is present each run. # Restore runs on every branch (PRs reuse main's snapshot); only main writes it (see save below). # The key pins every coordinate that affects the snapshot; bump the -v suffix if the emulator # options change, since GitHub caches are write-once per key. @@ -216,7 +223,7 @@ jobs: # below once, warm-booting from main's cached snapshot when present (a single boot, not two). - name: Create AVD and generate snapshot for caching if: github.ref == 'refs/heads/main' && steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0 with: api-level: 30 target: aosp_atd @@ -227,7 +234,7 @@ jobs: emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none script: echo "Generated AVD snapshot for caching." - name: Android instrumented tests on an emulator - uses: reactivecircus/android-emulator-runner@v2 + uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0 with: api-level: 30 target: aosp_atd diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ad8baf2..08ea691 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -21,7 +21,7 @@ on: # A newer push to the same PR ref cancels an in-flight scan instead of stacking concurrent analyses; # main runs are never cancelled, so every default-branch commit keeps its own recorded CodeQL result. concurrency: - group: codeql-${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} # Least privilege by default; the analyze job widens to what code-scanning upload needs. diff --git a/CLAUDE.md b/CLAUDE.md index a8e8df5..e23acac 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,7 +53,10 @@ conventions are wired into the build and are authoritative: `allWarningsAsErrors`, **explicit-API strict mode**, **binary-compatibility-validator** (`apiCheck`/`apiDump`), and a Kover line-coverage floor. Run `./gradlew build` to execute the full gate; `./gradlew apiDump` only after an **intentional** public-API change, and - commit the regenerated `api/*.api` snapshots alongside it. + commit the regenerated `api/*.api` snapshots alongside it. Regenerate the native + `kuri/api/kuri.klib.api` dump on **macOS**: running `apiDump` on a Linux/Windows host + silently drops the Apple klib targets from the merged dump, and the Linux `apiCheck` + won't flag the truncation. - **MIT license header in every source file** — each `.kt`/`.java`/`.kts` starts with the `Copyright (c) 2026 dexpace and Omar Aljarrah` / `SPDX-License-Identifier: MIT` block. This is a review convention; nothing enforces it automatically. diff --git a/kuri/build.gradle.kts b/kuri/build.gradle.kts index 1d7e7b2..e6808e8 100644 --- a/kuri/build.gradle.kts +++ b/kuri/build.gradle.kts @@ -229,6 +229,11 @@ kover { // klib but then skipping the comparison — wasted work that guaranteed nothing. Enabling it makes // `apiDump` emit the merged `api/kuri.klib.api` snapshot and `apiCheck` diff each native target's ABI // against it, so a source-compatible but ABI-breaking change to the native surface fails the build. +// The comparison only re-compiles the klib targets buildable on the CI host running `apiCheck`: the Linux +// quality-gate job verifies linuxX64/linuxArm64/mingw/js/wasm, and infers the Apple targets it cannot +// build there from the grouped dump rather than re-verifying them. That holds only because the public API +// is fully common (the dump has no per-target sections); a platform-specific public declaration would +// need `apiCheck` to also run on macOS to be guarded. Regenerate the merged dump on macOS (see CLAUDE.md). apiValidation { klib { enabled = true From 94b341648d437d55ff8c3c465c3af215c49a59f5 Mon Sep 17 00:00:00 2001 From: OmarAlJarrah Date: Mon, 13 Jul 2026 12:54:27 +0300 Subject: [PATCH 6/6] ci: enforce the Apple-target klib ABI on the macOS CI leg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The KLIB ABI check runs on the Linux quality-gate host, which can build the linuxX64/linuxArm64/mingw/js/wasm klibs but not the Apple ones, so it infers the Apple targets from the merged dump instead of re-verifying them. An Apple-target ABI break — or a dump regenerated on a non-macOS host, which silently drops the Apple targets from the merged header — would therefore pass CI unnoticed. Run klibApiCheck on the macOS native leg, the one host that can build every klib target, so the Apple ABI is verified for real rather than inferred. Update the build-script and CLAUDE.md notes to describe the host split, and complete the commit-prefix list to match .github/scripts/check-conventional-commit.sh. --- .github/workflows/ci.yml | 7 +++++++ CLAUDE.md | 11 +++++++---- kuri/build.gradle.kts | 12 +++++++----- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 166b99e..633c0db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -127,6 +127,12 @@ jobs: # execute — the device targets (iosArm64/tvosArm64/watchosArm64) and the x86_64 # iOS simulator (iosX64: disabled by Kotlin/Native on an arm64 host, and GitHub no # longer offers Intel macOS runners) — are compiled and linked via *TestBinaries. + # klibApiCheck runs here too: it re-verifies the Apple klib targets' ABI against the + # committed dump. The Linux quality-gate job can only *infer* those targets from the + # merged dump (it can't build them), so macOS is the only host that catches an Apple + # ABI break — or a dump accidentally regenerated on Linux/Windows, which silently + # drops the Apple targets. (The klib merge also cross-builds the non-Apple klibs on + # this host; those are redundant with the Linux gate but cheap.) tasks: >- macosArm64Test iosSimulatorArm64Test @@ -136,6 +142,7 @@ jobs: iosArm64TestBinaries tvosArm64TestBinaries watchosArm64TestBinaries + klibApiCheck steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 diff --git a/CLAUDE.md b/CLAUDE.md index e23acac..3f3414b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,8 +55,9 @@ conventions are wired into the build and are authoritative: the full gate; `./gradlew apiDump` only after an **intentional** public-API change, and commit the regenerated `api/*.api` snapshots alongside it. Regenerate the native `kuri/api/kuri.klib.api` dump on **macOS**: running `apiDump` on a Linux/Windows host - silently drops the Apple klib targets from the merged dump, and the Linux `apiCheck` - won't flag the truncation. + silently drops the Apple klib targets from the merged dump. The Linux `apiCheck` can't + see that truncation, but the macOS `native` CI leg runs `klibApiCheck` and fails on it, so + a dump regenerated on the wrong host is caught in CI rather than merged. - **MIT license header in every source file** — each `.kt`/`.java`/`.kts` starts with the `Copyright (c) 2026 dexpace and Omar Aljarrah` / `SPDX-License-Identifier: MIT` block. This is a review convention; nothing enforces it automatically. @@ -70,8 +71,10 @@ conventions are wired into the build and are authoritative: (synchronized pins carrier threads under Loom). Blocking calls must respect `Thread.interrupt()` — catch `InterruptedException`, restore the interrupt flag, and throw `InterruptedIOException` (or attach the interrupt as suppressed). -- **Commit style:** `feat:` / `test:` / `docs:` / `chore:` / `refactor:` prefixes (`merge:` - for merge commits). **PR titles follow the same prefixed style** (e.g. `docs: add CLAUDE.md`). +- **Commit style:** `feat:` / `fix:` / `docs:` / `test:` / `chore:` / `perf:` / `refactor:` / + `ci:` / `build:` / `style:` / `revert:` prefixes (`merge:` for merge commits), enforced by + `.github/scripts/check-conventional-commit.sh`. **PR titles follow the same prefixed style** + (e.g. `docs: add CLAUDE.md`). ## Generated data & codegen diff --git a/kuri/build.gradle.kts b/kuri/build.gradle.kts index e6808e8..c0ec576 100644 --- a/kuri/build.gradle.kts +++ b/kuri/build.gradle.kts @@ -229,11 +229,13 @@ kover { // klib but then skipping the comparison — wasted work that guaranteed nothing. Enabling it makes // `apiDump` emit the merged `api/kuri.klib.api` snapshot and `apiCheck` diff each native target's ABI // against it, so a source-compatible but ABI-breaking change to the native surface fails the build. -// The comparison only re-compiles the klib targets buildable on the CI host running `apiCheck`: the Linux -// quality-gate job verifies linuxX64/linuxArm64/mingw/js/wasm, and infers the Apple targets it cannot -// build there from the grouped dump rather than re-verifying them. That holds only because the public API -// is fully common (the dump has no per-target sections); a platform-specific public declaration would -// need `apiCheck` to also run on macOS to be guarded. Regenerate the merged dump on macOS (see CLAUDE.md). +// Enforcement splits across two CI hosts by what each can build. The Linux quality-gate `apiCheck` builds +// the linuxX64/linuxArm64/mingw/js/wasm klibs and diffs them, but it cannot build the Apple klibs, so it +// infers those from the merged dump rather than re-verifying them. The macOS `native` leg runs +// `klibApiCheck`, which *can* build every klib target — so the Apple ABI (macos/ios/tvos/watchos) is +// re-verified there for real, not inferred. Between them no target is left to inference only, and a dump +// accidentally regenerated on Linux/Windows (which silently drops the Apple targets) fails the macOS +// check. Regenerate the merged dump on macOS (see CLAUDE.md). apiValidation { klib { enabled = true