commit fbe3e9da789b48f37c733acc00582d2d8b220373
parent b6ab1d4101763281e49762924d1f6b2ac8db2384
Author: triesap <tyson@radroots.org>
Date: Mon, 10 Aug 2026 04:02:27 +0000
test: enforce HarvestCircle foundation boundaries
- audit final namespace, FFI identity, KMP targets, lifecycle, counters, and preferences
- reject generated outputs, credential-shaped material, and provenance outside exact allowlists
- run Git-aware and filesystem-only archive inventories from the normal check lane
- prove fail-closed fixtures and a complete standalone check without Git metadata
Diffstat:
5 files changed, 304 insertions(+), 4 deletions(-)
diff --git a/Makefile b/Makefile
@@ -5,10 +5,10 @@ CARGO ?= cargo
CARGO_MANIFEST := core/Cargo.toml
EXTBUILD ?= $(if $(shell cargo extbuild --version 2>/dev/null),cargo extbuild run --)
-.PHONY: help doctor lock metadata format format-fix lint test check build bindings dev run audit licenses package source-check package-check signing-check notarization-check release-check clean
+.PHONY: help doctor lock metadata format format-fix lint test check build bindings dev run audit licenses foundation-check package source-check package-check signing-check notarization-check release-check clean
help:
- @printf '%s\n' doctor lock metadata format format-fix lint test check build bindings dev run audit licenses package source-check package-check signing-check notarization-check release-check clean
+ @printf '%s\n' doctor lock metadata format format-fix lint test check build bindings dev run audit licenses foundation-check package source-check package-check signing-check notarization-check release-check clean
doctor:
$(if $(strip $(EXTBUILD)),cargo extbuild doctor,@:)
@@ -38,7 +38,7 @@ test: doctor
$(EXTBUILD) $(CARGO) test --manifest-path $(CARGO_MANIFEST) --workspace --locked
$(EXTBUILD) $(GRADLE) --no-daemon :app:shared:desktopTest :app:desktop:test
-check: format lint test
+check: format lint test foundation-check
$(EXTBUILD) $(GRADLE) --no-daemon :app:shared:check :app:desktop:check
build: doctor
@@ -63,6 +63,9 @@ licenses: doctor
$(EXTBUILD) $(CARGO) deny --manifest-path $(CARGO_MANIFEST) check --config core/deny.toml licenses sources
$(EXTBUILD) $(GRADLE) --no-daemon --no-parallel --no-configuration-cache :app:desktop:checkLicense
+foundation-check: doctor
+ $(EXTBUILD) $(GRADLE) --no-daemon :verifyFoundationBoundaries :verifyFoundationArchive
+
package: check
$(EXTBUILD) $(GRADLE) --no-daemon :app:desktop:verifyHostPackage
diff --git a/app/desktop/build.gradle.kts b/app/desktop/build.gradle.kts
@@ -896,7 +896,15 @@ val verifyReleaseBuildProvenance by tasks.registering(VerifyReleaseBuildProvenan
sourceDateEpoch.set(buildSourceDateEpoch)
}
val sourceReadiness by tasks.registering {
- dependsOn(":verifyProductCoordinates", ":verifyVerificationLanes", ":app:shared:check", "check", verifyUniFfiBindings)
+ dependsOn(
+ ":verifyProductCoordinates",
+ ":verifyVerificationLanes",
+ ":verifyFoundationBoundaries",
+ ":verifyFoundationArchive",
+ ":app:shared:check",
+ "check",
+ verifyUniFfiBindings,
+ )
}
val packageReadiness by tasks.registering {
dependsOn(verifyHostPackage, verifyReleaseBuildProvenance)
diff --git a/app/desktop/src/test/kotlin/org/harvestcircle/architecture/ProductNamespaceGuardTest.kt b/app/desktop/src/test/kotlin/org/harvestcircle/architecture/ProductNamespaceGuardTest.kt
@@ -131,6 +131,17 @@ class ProductNamespaceGuardTest {
}
private fun trackedFiles(root: Path): List<String> {
+ if (!Files.exists(root.resolve(".git"))) {
+ return Files.walk(root).use { paths ->
+ paths
+ .filter(Files::isRegularFile)
+ .map { root.relativize(it).toString().replace('\\', '/') }
+ .filter { relative ->
+ relative.split('/').none { it in setOf(".gradle", ".kotlin", "build", "target", "out") }
+ }.sorted()
+ .toList()
+ }
+ }
val process =
ProcessBuilder("git", "-C", root.toString(), "ls-files", "-z")
.redirectErrorStream(true)
diff --git a/build.gradle.kts b/build.gradle.kts
@@ -1,3 +1,4 @@
+import org.harvestcircle.gradle.VerifyFoundationBoundaries
import org.harvestcircle.gradle.VerifyProductCoordinates
import org.harvestcircle.gradle.VerifyVerificationLanes
@@ -38,6 +39,20 @@ val verifyVerificationLanes by tasks.registering(VerifyVerificationLanes::class)
policyFile.set(verificationLanesFile)
}
+val verifyFoundationBoundaries by tasks.registering(VerifyFoundationBoundaries::class) {
+ group = "verification"
+ description = "Audits tracked sources against the HarvestCircle foundation boundaries."
+ repositoryRoot.set(layout.projectDirectory)
+ gitAware.set(true)
+}
+
+val verifyFoundationArchive by tasks.registering(VerifyFoundationBoundaries::class) {
+ group = "verification"
+ description = "Audits a source-archive inventory without Git metadata."
+ repositoryRoot.set(layout.projectDirectory)
+ gitAware.set(false)
+}
+
providers.environmentVariable("EXT_BUILD_GRADLE_BUILD_DIR").orNull?.let { extBuildGradleRoot ->
layout.buildDirectory.set(file(extBuildGradleRoot).resolve("root"))
}
diff --git a/buildSrc/src/main/kotlin/org/harvestcircle/gradle/FoundationBoundaryAudit.kt b/buildSrc/src/main/kotlin/org/harvestcircle/gradle/FoundationBoundaryAudit.kt
@@ -0,0 +1,263 @@
+package org.harvestcircle.gradle
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Internal
+import org.gradle.api.tasks.TaskAction
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+import java.nio.file.Path
+import kotlin.io.path.extension
+import kotlin.io.path.name
+import kotlin.io.path.readText
+
+abstract class VerifyFoundationBoundaries : DefaultTask() {
+ @get:Internal
+ abstract val repositoryRoot: DirectoryProperty
+
+ @get:Input
+ abstract val gitAware: Property<Boolean>
+
+ @TaskAction
+ fun verify() {
+ val root = repositoryRoot.get().asFile.toPath()
+ val useGitInventory = gitAware.get() && Files.exists(root.resolve(".git"))
+ val paths = if (useGitInventory) trackedPaths(root) else archivePaths(root)
+ FoundationBoundaryAudit(root, paths).verify()
+ if (!gitAware.get()) {
+ verifyNegativeFixtures(root, paths)
+ }
+ }
+
+ private fun trackedPaths(root: Path): List<String> {
+ val process =
+ ProcessBuilder("git", "-C", root.toString(), "ls-files", "-z")
+ .redirectErrorStream(true)
+ .start()
+ val output = process.inputStream.readAllBytes()
+ require(process.waitFor() == 0) {
+ "Unable to enumerate tracked HarvestCircle sources: ${output.toString(StandardCharsets.UTF_8)}"
+ }
+ return output
+ .toString(StandardCharsets.UTF_8)
+ .split('\u0000')
+ .filter(String::isNotEmpty)
+ .sorted()
+ }
+
+ private fun archivePaths(root: Path): List<String> =
+ Files.walk(root).use { paths ->
+ paths
+ .filter { path ->
+ path != root &&
+ shouldInspect(root.relativize(path).toString().replace('\\', '/'))
+ }.map { root.relativize(it).toString().replace('\\', '/') }
+ .sorted()
+ .toList()
+ }
+
+ private fun shouldInspect(relative: String): Boolean {
+ val segments = relative.split('/')
+ return segments.none { it in setOf(".git", ".gradle", ".kotlin", ".idea", "build", "target", "out") }
+ }
+
+ private fun verifyNegativeFixtures(
+ root: Path,
+ paths: List<String>,
+ ) {
+ val fixtures =
+ listOf(
+ ".github/workflows/source.yml" to "name: source",
+ "app/shared/src/commonMain/kotlin/org/harvestcircle/application/Leak.kt" to
+ ("import org.harvestcircle." + "ffi.BuildInfoDto"),
+ "app/desktop/src/main/kotlin/org/harvestcircle/desktop/Blocking.kt" to
+ ("fun bad() = run" + "Blocking {}"),
+ "app/desktop/src/main/kotlin/org/harvestcircle/desktop/Counter.kt" to
+ ("val bad = Atomic" + "Long(0)"),
+ "core/target/generated/native.bin" to "generated",
+ "config/credentials/release.key" to "not-a-real-key",
+ )
+ fixtures.forEach { (path, source) ->
+ check(
+ runCatching {
+ FoundationBoundaryAudit(root, paths + path, mapOf(path to source)).verify()
+ }.isFailure,
+ ) { "Foundation audit accepted negative fixture $path" }
+ }
+ val provenancePath = "core/provenance/" + "stu" + "dio-import-v1.toml"
+ val altered = root.resolve(provenancePath).readText().replace("09065a610d95e57acdc895a14c07580fa099e7c3", "0".repeat(40))
+ check(
+ runCatching {
+ FoundationBoundaryAudit(root, paths, mapOf(provenancePath to altered)).verify()
+ }.isFailure,
+ ) { "Foundation audit accepted altered source provenance" }
+ }
+}
+
+private class FoundationBoundaryAudit(
+ private val root: Path,
+ paths: List<String>,
+ private val overrides: Map<String, String> = emptyMap(),
+) {
+ private val inventory = paths.distinct().sorted()
+ private val legacyProduct = "stu" + "dio"
+ private val provenancePath = "core/provenance/$legacyProduct-import-v1.toml"
+ private val legacyRepository = "https://github.com/radrootslabs/${legacyProduct}_app"
+ private val temporaryNamespace = listOf("org", "radroots", "harvestcircle").joinToString(".")
+ private val textExtensions =
+ setOf("gradle", "json", "kt", "kts", "lock", "md", "properties", "rs", "sql", "toml", "xml", "yaml", "yml")
+ private val textNames = setOf(".gitattributes", ".gitignore", "AGENTS.md", "Makefile", "gradlew", "gradlew.bat")
+
+ fun verify() {
+ val findings = mutableListOf<String>()
+ inventory.forEach { relative ->
+ verifyPath(relative, findings)
+ if (isText(relative)) {
+ val source = overrides[relative] ?: readText(relative)
+ verifyText(relative, source, findings)
+ }
+ }
+ verifyExactContracts(findings)
+ check(findings.isEmpty()) { findings.sorted().joinToString("\n") }
+ }
+
+ private fun verifyPath(
+ relative: String,
+ findings: MutableList<String>,
+ ) {
+ val normalized = relative.lowercase()
+ if (normalized.startsWith("docs/") || normalized.startsWith("spec/") ||
+ normalized.startsWith(".github/") || normalized.startsWith(".act/")
+ ) {
+ findings += "$relative: forbidden repository root"
+ }
+ if (normalized.startsWith("core/target/") || normalized.contains("/build/") ||
+ normalized.contains("generated/uniffi") || normalized.endsWith(".dylib") ||
+ normalized.endsWith(".so") || normalized.endsWith(".dll") || normalized.endsWith(".class")
+ ) {
+ findings += "$relative: generated build output must not be source controlled"
+ }
+ if (normalized.endsWith(".pem") || normalized.endsWith(".key") || normalized.endsWith(".p12") ||
+ normalized.endsWith(".pfx") || normalized.endsWith(".jks") || normalized.endsWith(".keystore") ||
+ normalized.endsWith(".env") || normalized.contains("/credentials/")
+ ) {
+ findings += "$relative: credential or secret-shaped source path"
+ }
+ if (relative != provenancePath && normalized.contains(legacyProduct)) {
+ findings += "$relative: legacy product name in source path"
+ }
+ val kotlinMarker = "/kotlin/"
+ if (normalized.startsWith("app/") && normalized.contains(kotlinMarker) && normalized.endsWith(".kt")) {
+ val packagePath = normalized.substringAfter(kotlinMarker)
+ if (!packagePath.startsWith("org/harvestcircle/")) {
+ findings += "$relative: Kotlin source is outside the final namespace"
+ }
+ }
+ }
+
+ private fun verifyText(
+ relative: String,
+ source: String,
+ findings: MutableList<String>,
+ ) {
+ if (relative != provenancePath) {
+ val inspected = if (relative == "core/Cargo.toml") source.replace(legacyRepository, "") else source
+ if (inspected.lowercase().contains(legacyProduct)) {
+ findings += "$relative: legacy product name outside the exact provenance allowlist"
+ }
+ }
+ if (source.contains(temporaryNamespace) || source.contains(temporaryNamespace.replace('.', '/'))) {
+ findings += "$relative: temporary product namespace"
+ }
+ val productionKotlin =
+ relative.startsWith("app/") &&
+ relative.endsWith(".kt") &&
+ (relative.contains("/src/main/") || relative.contains("/src/commonMain/") || relative.contains("/src/desktopMain/"))
+ if (productionKotlin && source.contains("run" + "Blocking")) {
+ findings += "$relative: blocking coroutine bridge in application source"
+ }
+ if (productionKotlin && (source.contains("Atomic" + "Long") || source.contains("desktop" + "-operation:"))) {
+ findings += "$relative: process-local operation counter"
+ }
+ if (relative.startsWith("app/shared/src/commonMain/") &&
+ listOf("org.harvestcircle." + "ffi", "com.sun." + "jna", "java.", "javax.").any(source::contains)
+ ) {
+ findings += "$relative: platform dependency in shared common source"
+ }
+ inheritedPreferenceTokens().filter(source.lowercase()::contains).forEach { token ->
+ findings += "$relative: inherited non-product preference $token"
+ }
+ val secretMarkers =
+ listOf(
+ "-----BEGIN " + "PRIVATE KEY-----",
+ "AWS_" + "SECRET_ACCESS_KEY=",
+ "gh" + "p_",
+ "sk_" + "live_",
+ )
+ if (secretMarkers.any(source::contains)) {
+ findings += "$relative: credential or private-key material in source text"
+ }
+ if (productionKotlin && source.lowercase().contains("nsec1")) {
+ findings += "$relative: secret key literal in production Kotlin"
+ }
+ }
+
+ private fun verifyExactContracts(findings: MutableList<String>) {
+ val cargo = text("core/Cargo.toml")
+ if (cargo.lineSequence().count { it.trim() == "repository = \"$legacyRepository\"" } != 1) {
+ findings += "core/Cargo.toml: legacy repository allowlist must be exact"
+ }
+ val provenance = text(provenancePath)
+ if (!provenance.contains("source_repository = \"$legacyRepository\"") ||
+ !provenance.contains("canonical_radroots_revision = \"09065a610d95e57acdc895a14c07580fa099e7c3\"") ||
+ !provenance.contains("foundation_baseline = \"a2038b3e25b9e34f0b8fd001f26a8ed10b5772cb\"")
+ ) {
+ findings += "$provenancePath: exact source provenance changed"
+ }
+ val uniFfi = text("core/crates/harvestcircle_ffi/uniffi.toml")
+ if (!uniFfi.contains("[crates.harvestcircle_ffi.bindings.kotlin]") ||
+ !uniFfi.contains("package_name = \"org.harvestcircle.ffi\"") ||
+ !uniFfi.contains("cdylib_name = \"harvestcircle_ffi\"")
+ ) {
+ findings += "core/crates/harvestcircle_ffi/uniffi.toml: final FFI identity changed"
+ }
+ val baseline = text("core/compatibility/harvestcircle-ffi-v4.properties")
+ if (!baseline.contains("contract.id=harvestcircle-desktop-ffi-v4") || !baseline.contains("contract.major=4")) {
+ findings += "core/compatibility/harvestcircle-ffi-v4.properties: FFI v4 identity changed"
+ }
+ val sharedBuild = text("app/shared/build.gradle.kts")
+ if (Regex("(?m)^\\s*jvm\\(\"desktop\"\\)").findAll(sharedBuild).count() != 1 ||
+ listOf("androidTarget", "iosArm", "iosX", "js(", "wasm").any(sharedBuild::contains)
+ ) {
+ findings += "app/shared/build.gradle.kts: shared KMP target boundary changed"
+ }
+ }
+
+ private fun inheritedPreferenceTokens(): List<String> {
+ val separator = "_"
+ return listOf(
+ listOf("use", "radroots", "dns").joinToString(separator),
+ listOf("use", "radroots", "subnets").joinToString(separator),
+ listOf("vpn", "on", "demand", "enabled").joinToString(separator),
+ listOf("run", "as", "exit", "node").joinToString(separator),
+ listOf("automatically", "check", "for", "updates").joinToString(separator),
+ listOf("update", "channel").joinToString(separator),
+ listOf("last", "update", "check", "summary").joinToString(separator),
+ listOf("alternate", "server", "url").joinToString(separator),
+ )
+ }
+
+ private fun isText(relative: String): Boolean {
+ val path = Path.of(relative)
+ return path.extension in textExtensions || path.name in textNames
+ }
+
+ private fun text(relative: String): String = overrides[relative] ?: readText(relative)
+
+ private fun readText(relative: String): String {
+ val path = root.resolve(relative)
+ return if (Files.isRegularFile(path)) path.readText() else ""
+ }
+}