merge: ticket 17 Linux saved-device core contract

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-11 05:13:00 +02:00
4 changed files with 953 additions and 1 deletions

View File

@@ -0,0 +1,114 @@
package com.vnidrop.app.core
import java.nio.file.Files
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
import uniffi.vnidrop.CoreEvent
import uniffi.vnidrop.CoreEventSink
import uniffi.vnidrop.VnidropCore
import uniffi.vnidrop.defaultCoreLimits
import uniffi.vnidrop.defaultCoreNetworkConfig
/**
* Linux host harness for the experimental saved-device core contract (ticket 17).
*
* Identity restart against Secret Service runs only on Linux (CI host). Binding
* hygiene assertions run on every JVM so macOS/Windows desktop checks stay useful.
*/
class SavedDeviceCoreContractLinuxTest {
@Test
fun experimentalInitRestartsSameIdentityOnLinux() {
val isLinux = System.getProperty("os.name").orEmpty().lowercase().contains("linux")
if (!isLinux) {
return
}
val coreDir = Files.createTempDirectory("vnidrop-linux-contract")
val sink = object : CoreEventSink {
override fun onEvent(event: CoreEvent) = Unit
}
val first = VnidropCore.initializeWithExperimentalSavedDevices(
appDataDir = coreDir.toString(),
eventSink = sink,
limits = defaultCoreLimits(),
networkConfig = defaultCoreNetworkConfig(),
)
val endpointId = try {
val id = first.status().endpointId
assertTrue(id.isNotBlank())
assertFalse(coreDir.resolve("iroh.secret").toFile().exists())
id
} finally {
first.shutdown()
}
val second = VnidropCore.initializeWithExperimentalSavedDevices(
appDataDir = coreDir.toString(),
eventSink = sink,
limits = defaultCoreLimits(),
networkConfig = defaultCoreNetworkConfig(),
)
try {
assertEquals(endpointId, second.status().endpointId)
assertFalse(coreDir.resolve("iroh.secret").toFile().exists())
// Public surface used by the contract is present on bindings.
second.listSavedDevices()
second.listDeviceRelationships()
second.listPairingEligibilities()
second.listBlockedDevices()
second.listTargetedTransfers()
second.listEvents(null)
} finally {
second.shutdown()
coreDir.toFile().deleteRecursively()
}
}
@Test
fun publicBindingsOmitRawSecretsAndGenericMutation() {
val instanceMethods = VnidropCore::class.java.methods.map { it.name }.toSet()
val companionMethods = VnidropCore.Companion::class.java.methods.map { it.name }.toSet()
val methodNames = instanceMethods + companionMethods
for (forbidden in listOf(
"putSecret",
"getSecret",
"executeSql",
"executeSQL",
"mutateState",
"applyRawState",
"setRawState",
"grantSecret",
"rawSecret",
"pairingCapabilityBytes",
)) {
assertFalse(
methodNames.contains(forbidden),
"VnidropCore must not expose $forbidden",
)
}
assertTrue(
companionMethods.contains("initializeWithExperimentalSavedDevices"),
"experimental saved-device init must be on the public binding",
)
assertTrue(
instanceMethods.contains("setSavedDeviceLabel"),
"typed rename must be on the public binding",
)
assertTrue(
instanceMethods.any { it.startsWith("listEvents") },
"event listing must be on the public binding",
)
val eventFields = CoreEvent::class.java.declaredFields.map { it.name }.toSet()
assertTrue(eventFields.contains("revision"), "CoreEvent must carry revision")
assertTrue(eventFields.contains("id"), "CoreEvent must carry stable id")
for (forbidden in listOf("secret", "grantBytes", "capabilityBytes", "secretMaterial")) {
assertFalse(
eventFields.any { it.equals(forbidden, ignoreCase = true) },
"CoreEvent must not expose $forbidden",
)
}
}
}