app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

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:
MMakefile | 9++++++---
Mapp/desktop/build.gradle.kts | 10+++++++++-
Mapp/desktop/src/test/kotlin/org/harvestcircle/architecture/ProductNamespaceGuardTest.kt | 11+++++++++++
Mbuild.gradle.kts | 15+++++++++++++++
AbuildSrc/src/main/kotlin/org/harvestcircle/gradle/FoundationBoundaryAudit.kt | 263+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 "" + } +}