app

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

commit 66b603cf145bbb4ade64da622e440daedb4776c8
parent b3da37d0881d2c54d1a90811b8c001c455d9f9de
Author: triesap <tyson@radroots.org>
Date:   Sun,  9 Aug 2026 18:07:55 +0000

core: complete local Rust ownership

- remove the superseded source-lock crate manifest and consumer marker
- build native libraries and UniFFI bindings directly from HarvestCircle core
- align workspace lint policy with the imported producer contract
- rename the Gradle root and retain immutable shared Radroots Git edges

Diffstat:
D.radroots-consumer-root | 1-
MAGENTS.md | 16++++++----------
Mapp/desktop/build.gradle.kts | 130++++++++++++++-----------------------------------------------------------------
Mcore/Cargo.lock | 13-------------
Mcore/Cargo.toml | 9++++++---
Dcore/crates/source_lock/Cargo.toml | 20--------------------
Dcore/crates/source_lock/src/lib.rs | 1-
Dradroots.lib.source-lock.v1.toml | 9---------
Msettings.gradle.kts | 2+-
9 files changed, 36 insertions(+), 165 deletions(-)

diff --git a/.radroots-consumer-root b/.radroots-consumer-root @@ -1 +0,0 @@ -studio diff --git a/AGENTS.md b/AGENTS.md @@ -28,10 +28,6 @@ absolute host paths, or an enclosing monorepo layout. - `core/Cargo.toml`, `core/Cargo.lock`, `core/rust-toolchain.toml`, and the product crates under `core/crates/**` own the Rust workspace inputs. -- While the ownership migration is in progress, - `radroots.lib.source-lock.v1.toml` and `.radroots-consumer-root` remain - transitional inputs. Remove them, the source-lock placeholder crate, and - external Studio crate edges once every product crate is local and green. - Gradle settings, build scripts, the version catalog, wrapper properties, policy configuration, and `core/compatibility/v5-baseline.properties` own the desktop build, dependency, compatibility, and package inputs. Kotlin and @@ -47,8 +43,8 @@ absolute host paths, or an enclosing monorepo layout. agree. - Never use a path dependency, floating branch or tag, private mirror, implicit sibling override, dirty source cache, or unrecorded native binary. - A local commit is not a release input until it is publicly reachable and its - governed source-lock evidence verifies. + Local product crates are workspace path dependencies; shared Radroots + packages remain immutable public Git dependencies. - Human specifications, decisions, runbooks, migration history, qualification records, and execution evidence are parent-owned and absent from standalone clones. Physical or tracked `docs/**`, `.github/**`, and `.act/**` roots are @@ -85,8 +81,8 @@ check in generated bindings or native binaries as a source substitute. ## Change and command rules -Inspect status, all relevant manifests and locks, source-lock and compatibility -inputs, Gradle tasks, native generation logic, source, tests, and package +Inspect status, all relevant manifests and locks, compatibility inputs, Gradle +tasks, native generation logic, source, tests, and package configuration before editing. Make one coherent, reviewable change at a time; keep implementation, tests, generated outputs, dependency evidence, and public behavior aligned while preserving unrelated work. @@ -107,8 +103,8 @@ silently switch sources when offline. Never claim a lane passed unless it ran successfully; report network, toolchain, platform, advisory-service, package, or external-artifact blockers exactly. -Verify exact source-lock/manifests/lock agreement, generated binding and native -artifact freshness when affected, compatibility and architecture guards, +Verify exact manifests/lock agreement, generated binding and native artifact +freshness when affected, compatibility and architecture guards, license/source policy, zero forbidden roots, `git diff --check`, and final status and diff. Do not treat parent-only workflow proof as a substitute for standalone repository validation. diff --git a/app/desktop/build.gradle.kts b/app/desktop/build.gradle.kts @@ -86,24 +86,29 @@ val immutableCargoArguments = listOf("--locked") } -val sourceLockFile = rootProject.layout.projectDirectory.file("radroots.lib.source-lock.v1.toml") - -fun sourceLockValue(key: String): String { +val rustCoreSource = + rootProject.layout.projectDirectory + .dir("core") + .asFile +val rustManifest = rustCoreSource.resolve("Cargo.toml") + +fun workspacePackageValue(key: String): String { + val workspacePackage = + rustManifest + .readText() + .substringAfter("[workspace.package]", missingDelimiterValue = "") + .substringBefore("\n[") + require(workspacePackage.isNotBlank()) { "Cargo manifest is missing [workspace.package]" } val expression = Regex("""(?m)^${Regex.escape(key)}\s*=\s*"([^"]+)"\s*$""") return expression - .find(sourceLockFile.asFile.readText()) + .find(workspacePackage) ?.groupValues ?.get(1) - ?: throw GradleException("Source lock is missing $key") + ?: throw GradleException("Cargo workspace package metadata is missing $key") } -val radrootsLibRepository = sourceLockValue("repository") -val radrootsLibRevision = sourceLockValue("revision") -val radrootsLibVersion = sourceLockValue("version") -val radrootsLibSource = file(extBuildGradleRoot).resolve("source/lib") -val rustManifest = radrootsLibSource.resolve("Cargo.toml") val compatibilityBaseline = rootProject.layout.projectDirectory.file("core/compatibility/v5-baseline.properties") -val appVersion = radrootsLibVersion +val appVersion = workspacePackageValue("version") val macOsBuildVersion = "1" val baseline = Properties().apply { @@ -127,102 +132,17 @@ val applicationNamespace = baselineValue("package.namespace") version = appVersion val rustSources = - fileTree(radrootsLibSource) { + fileTree(rustCoreSource) { include( "Cargo.toml", "Cargo.lock", "rust-toolchain.toml", - "contracts/crates/catalog.v1.toml", + "compatibility/**", "crates/studio_*/**", - "tools/xtask/**", ) exclude("target/**") } -abstract class SyncRadrootsLibSource : DefaultTask() { - @get:Input - abstract val repository: Property<String> - - @get:Input - abstract val revision: Property<String> - - @get:Input - abstract val offline: Property<Boolean> - - @get:OutputDirectory - abstract val sourceDirectory: DirectoryProperty - - @TaskAction - fun sync() { - val source = sourceDirectory.get().asFile - if (!source.resolve(".git").isDirectory) { - require(!offline.get()) { "Canonical source is not cached and offline mode is enabled" } - source.parentFile.mkdirs() - command(source.parentFile, "git", "clone", "--filter=blob:none", "--no-checkout", repository.get(), source.absolutePath) - } - require(command(source, "git", "remote", "get-url", "origin").trim() == repository.get()) { - "Canonical source cache origin does not match the source lock" - } - if (!offline.get()) { - command(source, "git", "fetch", "--force", "--no-tags", "origin", revision.get()) - } - command(source, "git", "cat-file", "-e", "${revision.get()}^{commit}") - command(source, "git", "checkout", "--detach", revision.get()) - require(command(source, "git", "rev-parse", "HEAD").trim() == revision.get()) { - "Materialized source revision does not match the source lock" - } - require(command(source, "git", "status", "--porcelain", "--untracked-files=all").isBlank()) { - "Materialized source tree is not clean" - } - } - - private fun command( - workingDirectory: File, - vararg command: String, - ): String { - val process = - ProcessBuilder(*command) - .directory(workingDirectory) - .redirectErrorStream(true) - .start() - val output = process.inputStream.bufferedReader().use { it.readText() } - require(process.waitFor() == 0) { - "${command.joinToString(" ")} failed: ${output.trim()}" - } - return output - } -} - -val syncRadrootsLibSource by tasks.registering(SyncRadrootsLibSource::class) { - repository.set(radrootsLibRepository) - revision.set(radrootsLibRevision) - offline.set( - radrootsOffline, - ) - sourceDirectory.set(radrootsLibSource) - outputs.upToDateWhen { false } -} - -val validateRadrootsSourceLock by tasks.registering(Exec::class) { - dependsOn(syncRadrootsLibSource) - workingDir(radrootsLibSource) - commandLine( - "cargo", - "run", - "--manifest-path", - rustManifest.absolutePath, - "-p", - "xtask", - *immutableCargoArguments.toTypedArray(), - "--", - "source-lock", - "--consumer-root", - rootProject.projectDir.absolutePath, - ) - inputs.file(sourceLockFile) - inputs.file(rootProject.layout.projectDirectory.file("core/Cargo.lock")) -} - data class NativeTarget( val libraryName: String, val jnaPrefix: String, @@ -263,8 +183,7 @@ val rustReleaseLibrary = file(cargoTargetRoot).resolve("release/$rustLibraryName val jnaPlatformPrefix = nativeTarget.jnaPrefix val buildRustCoreDebug by tasks.registering(Exec::class) { - dependsOn(validateRadrootsSourceLock) - workingDir(radrootsLibSource) + workingDir(rustCoreSource) commandLine( "cargo", "build", @@ -274,14 +193,12 @@ val buildRustCoreDebug by tasks.registering(Exec::class) { "radroots_studio_ffi", *immutableCargoArguments.toTypedArray(), ) - environment("RADROOTS_SOURCE_SHA", radrootsLibRevision) inputs.files(rustSources) outputs.file(rustDebugLibrary) } val buildRustCoreRelease by tasks.registering(Exec::class) { - dependsOn(validateRadrootsSourceLock) - workingDir(radrootsLibSource) + workingDir(rustCoreSource) commandLine( "cargo", "build", @@ -292,7 +209,6 @@ val buildRustCoreRelease by tasks.registering(Exec::class) { "radroots_studio_ffi", *immutableCargoArguments.toTypedArray(), ) - environment("RADROOTS_SOURCE_SHA", radrootsLibRevision) inputs.files(rustSources) outputs.file(rustReleaseLibrary) } @@ -304,7 +220,7 @@ val cleanGeneratedUniFfiKotlin by tasks.registering(Delete::class) { } val generateUniFfiKotlin by tasks.registering(Exec::class) { dependsOn(buildRustCoreDebug, cleanGeneratedUniFfiKotlin) - workingDir(radrootsLibSource) + workingDir(rustCoreSource) commandLine( "cargo", "run", @@ -321,13 +237,13 @@ val generateUniFfiKotlin by tasks.registering(Exec::class) { "--metadata-no-deps", "--no-format", "--config", - radrootsLibSource.resolve("crates/studio_ffi/uniffi.toml").absolutePath, + rustCoreSource.resolve("crates/studio_ffi/uniffi.toml").absolutePath, "--out-dir", generatedUniFfiKotlin.get().asFile.absolutePath, rustDebugLibrary.absolutePath, ) inputs.file(rustDebugLibrary) - inputs.file(radrootsLibSource.resolve("crates/studio_ffi/uniffi.toml")) + inputs.file(rustCoreSource.resolve("crates/studio_ffi/uniffi.toml")) outputs.dir(generatedUniFfiKotlin) } diff --git a/core/Cargo.lock b/core/Cargo.lock @@ -2081,19 +2081,6 @@ dependencies = [ ] [[package]] -name = "radroots_studio_source_lock" -version = "0.1.0-alpha" -dependencies = [ - "radroots_studio_application", - "radroots_studio_domain", - "radroots_studio_ffi", - "radroots_studio_nostr", - "radroots_studio_preferences", - "radroots_studio_runtime", - "radroots_studio_storage", -] - -[[package]] name = "radroots_studio_storage" version = "0.1.0-alpha" dependencies = [ diff --git a/core/Cargo.toml b/core/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "crates/source_lock", "crates/studio_application", "crates/studio_domain", "crates/studio_ffi", @@ -25,9 +24,13 @@ authors = ["Tyson Lupul <tyson@radroots.org>"] unsafe_code = "forbid" unexpected_cfgs = { level = "warn", check-cfg = ['cfg(coverage_nightly)'] } +[workspace.lints.rustdoc] +broken_intra_doc_links = "deny" + [workspace.lints.clippy] -all = "deny" -pedantic = "deny" +dbg_macro = "deny" +todo = "deny" +unimplemented = "deny" [workspace.dependencies] radroots_studio_application = { path = "crates/studio_application", version = "=0.1.0-alpha" } diff --git a/core/crates/source_lock/Cargo.toml b/core/crates/source_lock/Cargo.toml @@ -1,20 +0,0 @@ -[package] -name = "radroots_studio_source_lock" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -repository.workspace = true -publish = false - -[dependencies] -radroots_studio_application.workspace = true -radroots_studio_domain.workspace = true -radroots_studio_ffi.workspace = true -radroots_studio_nostr.workspace = true -radroots_studio_preferences.workspace = true -radroots_studio_runtime.workspace = true -radroots_studio_storage.workspace = true - -[lints] -workspace = true diff --git a/core/crates/source_lock/src/lib.rs b/core/crates/source_lock/src/lib.rs @@ -1 +0,0 @@ -#![no_std] diff --git a/radroots.lib.source-lock.v1.toml b/radroots.lib.source-lock.v1.toml @@ -1,9 +0,0 @@ -schema = "radroots.lib.source-lock.v1" -repository = "https://github.com/radrootslabs/lib" -revision = "09065a610d95e57acdc895a14c07580fa099e7c3" -architecture = "radroots.crates.release.v2" -workspace_catalog_sha256 = "8c27cebf6825f9ed74e122513c661f6dd31837dddb39d0988014121d8ab05e75" -version = "0.1.0-alpha" -source_archive_sha256 = "68badd1fb02d9396682d368e62dfe79969d8da48529760789c823fb4ab54aea3" -lockfile = "core/Cargo.lock" -lockfile_sha256 = "f84789b2a890ae3cd082dbce464adb582c4ebd4dcebedf52c2381476294f633a" diff --git a/settings.gradle.kts b/settings.gradle.kts @@ -37,7 +37,7 @@ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } -rootProject.name = "studio_app" +rootProject.name = "harvestcircle" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")