commit 2554ed9c1c37372c05badfc75bd00c28a0de986f parent c08d18ea569351dddeef70d4c1410708daf067b6 Author: triesap <tyson@radroots.org> Date: Wed, 26 Aug 2026 01:19:46 +0000 product: finalize public HarvestCircle identity - Point product links, Cargo metadata, and provenance at the canonical repository. - Rename the RoundBuilder surface and remove transition-era identity exceptions. - Pin the exact promoted Radroots Lib revision with a checked-in source lock. - Refresh dependency verification and tests for the fail-closed boundary. Diffstat:
22 files changed, 211 insertions(+), 470 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md @@ -38,8 +38,9 @@ local artifacts, absolute host paths, or an enclosing monorepo layout. ## Machine authority and generated inputs -- `core/Cargo.toml`, `core/Cargo.lock`, `core/rust-toolchain.toml`, and the - product crates under `core/crates/**` own the Rust workspace inputs. +- `core/Cargo.toml`, `core/Cargo.lock`, `core/rust-toolchain.toml`, + `radroots.lib.source-lock.v1.toml`, and the product crates under + `core/crates/**` own the Rust workspace inputs. - Gradle settings, build scripts, the version catalog, wrapper properties, policy configuration, and `config/product/harvestcircle-v1.properties` own the desktop build, dependency, product-coordinate, and package inputs. diff --git a/NOTICE b/NOTICE @@ -2,13 +2,6 @@ HarvestCircle Copyright © 2026 HarvestCircle contributors. -This repository was derived from Radroots Studio application work and imports -product-specific Rust foundations whose provenance is recorded in: - -```text -core/provenance/studio-import-v1.toml -``` - Canonical reusable Radroots dependencies remain sourced from the public `radrootslabs/lib` repository at exact immutable revisions. diff --git a/README.md b/README.md @@ -42,7 +42,7 @@ required. Release, signing, and notarization checks are governed-only. ## Development branch -Active implementation currently proceeds on `dev`. +Active implementation proceeds on `master`. ## Project documentation diff --git a/app/desktop/src/test/kotlin/org/harvestcircle/architecture/MachineProvenanceTest.kt b/app/desktop/src/test/kotlin/org/harvestcircle/architecture/MachineProvenanceTest.kt @@ -9,31 +9,28 @@ import kotlin.test.assertTrue class MachineProvenanceTest { @Test - fun sourceImportProvenanceUsesVerifiedImmutableCoordinates() { + fun sourceProvenanceUsesVerifiedImmutableCoordinates() { val root = findProvenanceRepositoryRoot() - val legacyProduct = "stu" + "dio" - val provenance = root.resolve("core/provenance/$legacyProduct-import-v1.toml").readText() + val provenance = root.resolve("core/provenance/harvestcircle-v1.toml").readText() assertTrue(provenance.contains("schema = \"harvestcircle.source_provenance.v1\"")) assertTrue( provenance.contains( - "foundation_baseline = \"a2038b3e25b9e34f0b8fd001f26a8ed10b5772cb\"", + "source_repository = \"https://github.com/radrootslabs/harvestcircle\"", ), ) assertTrue( provenance.contains( - "canonical_radroots_revision = \"09065a610d95e57acdc895a14c07580fa099e7c3\"", + "foundation_baseline = \"c08d18ea569351dddeef70d4c1410708daf067b6\"", + ), + ) + assertTrue( + provenance.contains( + "canonical_radroots_revision = \"be9db78e060ebc0000fa7827ac32efa3f6504f53\"", ), ) assertEquals(8, Regex("(?m)^\\[\\[import]]$").findAll(provenance).count()) assertEquals(8, Regex("(?m)^commit = \"[0-9a-f]{40}\"$").findAll(provenance).count()) - assertEquals( - 1, - Regex( - "(?m)^source_repository = \"https://github.com/radrootslabs/${legacyProduct}_app\"$", - ).findAll(provenance) - .count(), - ) } } diff --git a/app/desktop/src/test/kotlin/org/harvestcircle/architecture/ProductNamespaceGuardTest.kt b/app/desktop/src/test/kotlin/org/harvestcircle/architecture/ProductNamespaceGuardTest.kt @@ -92,10 +92,6 @@ class ProductNamespaceGuardTest { val legacyProduct = "stu" + "dio" val temporaryNamespace = listOf("org", "radroots", "harvestcircle").joinToString(".") val temporaryPath = temporaryNamespace.replace('.', '/') - val repositoryUrlException = "https://github.com/radrootslabs/" + legacyProduct + "_app" - val provenanceException = "core/provenance/" + legacyProduct + "-import-v1.toml" - val designProvenanceException = "config/design/source_baseline_v1.toml" - val designAuditException = "tools/xtask/src/lib.rs" val textExtensions = setOf( "gradle", @@ -119,11 +115,7 @@ class ProductNamespaceGuardTest { trackedFiles(root).flatMap { relative -> buildList { val normalizedRelative = relative.lowercase() - if ( - relative != provenanceException && - relative != designProvenanceException && - normalizedRelative.contains(legacyProduct) - ) { + if (normalizedRelative.contains(legacyProduct)) { add("$relative: legacy product name in tracked path") } if (normalizedRelative.contains(temporaryPath)) { @@ -131,25 +123,8 @@ class ProductNamespaceGuardTest { } val path = root.resolve(relative) - if ( - relative != provenanceException && - relative != designProvenanceException && - relative != designAuditException && - (path.extension in textExtensions || path.name in textNames) - ) { - var inspected = path.readText().replace(repositoryUrlException, "") - inspected = - inspected - .replace("round_${legacyProduct}_screen", "") - .replace("Round${legacyProduct.replaceFirstChar { it.uppercase() }}", "") - .replace("${legacyProduct.replaceFirstChar { it.uppercase() }}Template", "") - if (relative == "NOTICE") { - val legacyDisplayName = legacyProduct.replaceFirstChar { it.uppercase() } - inspected = - inspected - .replace("Radroots $legacyDisplayName application work", "") - .replace(provenanceException, "") - } + if (path.extension in textExtensions || path.name in textNames) { + val inspected = path.readText() if (inspected.lowercase().contains(legacyProduct)) { add("$relative: legacy product name in tracked text") } diff --git a/app/shared/src/commonMain/kotlin/org/harvestcircle/product/SurfaceRegistry.kt b/app/shared/src/commonMain/kotlin/org/harvestcircle/product/SurfaceRegistry.kt @@ -177,9 +177,9 @@ enum class ScreenKey( FeatureAvailability.DeferredCollective, ), ), - RoundStudio( + RoundBuilder( screen( - "round_studio_screen", + "round_builder_screen", LayoutKind.Dashboard, WorkspaceKind.Farm, NavigationKind.Permanent, diff --git a/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationSettingsScreen.kt b/app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationSettingsScreen.kt @@ -27,8 +27,8 @@ import org.harvestcircle.identities.ui.HarvestCirclePlatformActions import org.harvestcircle.navigation.SettingsSection object HarvestCircleProjectLinks { - const val SOURCE = "https://github.com/radrootslabs/studio_app" - const val LICENCE = "https://github.com/radrootslabs/studio_app/blob/dev/LICENSE" + const val SOURCE = "https://github.com/radrootslabs/harvestcircle" + const val LICENCE = "https://github.com/radrootslabs/harvestcircle/blob/master/LICENSE" } data class FoundationSettingsActions( diff --git a/app/shared/src/commonTest/kotlin/org/harvestcircle/product/SurfaceRegistryTest.kt b/app/shared/src/commonTest/kotlin/org/harvestcircle/product/SurfaceRegistryTest.kt @@ -48,7 +48,7 @@ private val expectedScreenKeys = "commitment_screen", "allocation_screen", "farm_overview_screen", - "round_studio_screen", + "round_builder_screen", "live_round_screen", "pickup_desk_screen", "round_outcome_screen", diff --git a/build-logic/plugins/src/functionalTest/kotlin/org/harvestcircle/buildlogic/plugins/ConventionPluginSmokeTest.kt b/build-logic/plugins/src/functionalTest/kotlin/org/harvestcircle/buildlogic/plugins/ConventionPluginSmokeTest.kt @@ -584,7 +584,7 @@ class ConventionPluginSmokeTest { storage.schema.current=10 product.version=0.1.0-alpha package.version=1.0.0 - source.provenance_digest=db238195b4a5938a8d4d9ac5681c4b125e65c57aa8133ad03e59da4e4bd062bc - source.foundation_baseline=a2038b3e25b9e34f0b8fd001f26a8ed10b5772cb + source.provenance_digest=daded0256c87be5a413358b346498dcecb412d4eff53d2998999d26ec20f3d40 + source.foundation_baseline=c08d18ea569351dddeef70d4c1410708daf067b6 """.trimIndent() + "\n" } diff --git a/build-logic/plugins/src/main/kotlin/org/harvestcircle/buildlogic/plugins/HarvestCircleRootPlugin.kt b/build-logic/plugins/src/main/kotlin/org/harvestcircle/buildlogic/plugins/HarvestCircleRootPlugin.kt @@ -13,7 +13,6 @@ public class HarvestCircleRootPlugin : Plugin<Project> { val ffiCompatibilityBaselineFile = target.layout.projectDirectory.file("core/compatibility/harvestcircle-ffi-v4.properties") val verificationLanesFile = target.layout.projectDirectory.file("config/verification/lanes-v2.properties") - val legacyProduct = "stu" + "dio" val verifyProductCoordinates = target.tasks.register("verifyProductCoordinates", VerifyProductCoordinates::class.java) { task -> @@ -25,7 +24,7 @@ public class HarvestCircleRootPlugin : Plugin<Project> { ) task.ffiBaselineFile.set(ffiCompatibilityBaselineFile) task.sourceProvenanceFile.set( - target.layout.projectDirectory.file("core/provenance/$legacyProduct-import-v1.toml"), + target.layout.projectDirectory.file("core/provenance/harvestcircle-v1.toml"), ) } diff --git a/config/design/harvestcircle-v1.toml b/config/design/harvestcircle-v1.toml @@ -0,0 +1,12 @@ +schema = "harvestcircle.design.v1" +repository = "https://github.com/radrootslabs/harvestcircle" +baseline_revision = "c08d18ea569351dddeef70d4c1410708daf067b6" +license = "GPL-3.0-only" +golden_host = "macos-aarch64" +golden_status = "verified" +golden_light_sha256 = "96e1ef5dd8b5cb14e47471a737a1e57ab0543b7f3aa79b865051e4740a2ee57a" +golden_dark_sha256 = "6a85cd890109b11de6f647dca91cb616651e362aa0802c40aa6aee7f678451c9" +design_system_root = "app/design_system" +design_catalog_root = "tools/design_catalog" +application_shell_root = "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell" +golden_test_path = "app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/HarvestCircleMacGoldenTest.kt" diff --git a/config/design/source_baseline_v1.toml b/config/design/source_baseline_v1.toml @@ -1,68 +0,0 @@ -schema = "harvestcircle.design_source_baseline.v1" -source_product = "Studio" -source_repository = "https://github.com/radrootslabs/studio_app" -source_head = "8ae5d8a0377c5673038a20b82b87c314370f0395" -source_state = "clean" -snapshot_sha256 = "c2fe49f3c3ea43105cb2fff4a67c7cd9c21561c71825440a91654a0c1b12e3b8" -snapshot_file_count = 102 -source_license = "GPL-3.0-only" -golden_host = "macos-aarch64" -golden_status = "verified" -golden_source_snapshot_sha256 = "c2fe49f3c3ea43105cb2fff4a67c7cd9c21561c71825440a91654a0c1b12e3b8" -golden_light_sha256 = "96e1ef5dd8b5cb14e47471a737a1e57ab0543b7f3aa79b865051e4740a2ee57a" -golden_dark_sha256 = "6a85cd890109b11de6f647dca91cb616651e362aa0802c40aa6aee7f678451c9" - -[[mapping]] -source = "core/designsystem/src/commonMain" -destination = "app/design_system/src/commonMain" -disposition = "owned-port" - -[[mapping]] -source = "core/designsystem/src/commonTest" -destination = "app/design_system/src/commonTest" -disposition = "owned-port" - -[[mapping]] -source = "core/designsystem/src/jvmMain" -destination = "app/design_system/src/desktopMain" -disposition = "owned-port" - -[[mapping]] -source = "tools/designcatalog/src/commonMain" -destination = "tools/design_catalog/src/commonMain" -disposition = "owned-port" - -[[mapping]] -source = "tools/designcatalog/src/jvmMain" -destination = "tools/design_catalog/src/desktopMain" -disposition = "owned-port" - -[[mapping]] -source = "shared/src/commonMain/kotlin/com/radroots/studio/ui/shell" -destination = "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell" -disposition = "visual-reference" - -[[mapping]] -source = "shared/src/commonTest/kotlin/com/radroots/studio/ui/shell" -destination = "app/shared/src/commonTest/kotlin/org/harvestcircle/ui/shell" -disposition = "test-reference" - -[[mapping]] -source = "shared/src/jvmMain/kotlin/com/radroots/studio/ui/shell" -destination = "app/shared/src/desktopMain/kotlin/org/harvestcircle/ui/shell" -disposition = "visual-reference" - -[[mapping]] -source = "shared/src/commonMain/kotlin/com/radroots/studio/ui/dashboard" -destination = "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell" -disposition = "visual-reference" - -[[mapping]] -source = "shared/src/commonMain/composeResources/values/strings.xml" -destination = "audit-only/product-copy" -disposition = "reject-product-copy" - -[[mapping]] -source = "shared/src/webMain/kotlin/com/radroots/studio/ui/shell" -destination = "audit-only/web-target" -disposition = "reject-platform-target" diff --git a/core/Cargo.lock b/core/Cargo.lock @@ -1936,7 +1936,7 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "radroots_identity" version = "0.1.0-alpha" -source = "git+https://github.com/radrootslabs/lib?rev=09065a610d95e57acdc895a14c07580fa099e7c3#09065a610d95e57acdc895a14c07580fa099e7c3" +source = "git+https://github.com/radrootslabs/lib?rev=be9db78e060ebc0000fa7827ac32efa3f6504f53#be9db78e060ebc0000fa7827ac32efa3f6504f53" dependencies = [ "k256", "thiserror 2.0.20", diff --git a/core/Cargo.toml b/core/Cargo.toml @@ -17,7 +17,7 @@ version = "0.1.0-alpha" edition = "2024" rust-version = "1.97.1" license = "GPL-3.0-only" -repository = "https://github.com/radrootslabs/studio_app" +repository = "https://github.com/radrootslabs/harvestcircle" homepage = "https://radroots.org" authors = ["Tyson Lupul <tyson@radroots.org>"] @@ -43,7 +43,7 @@ harvestcircle_runtime = { path = "crates/harvestcircle_runtime", version = "=0.1 harvestcircle_storage = { path = "crates/harvestcircle_storage", version = "=0.1.0-alpha" } harvestcircle_test_bridge = { path = "crates/harvestcircle_test_bridge", version = "=0.1.0-alpha" } harvestcircle_uniffi_bindgen = { path = "crates/harvestcircle_uniffi_bindgen", version = "=0.1.0-alpha" } -radroots_identity = { git = "https://github.com/radrootslabs/lib", rev = "09065a610d95e57acdc895a14c07580fa099e7c3", version = "=0.1.0-alpha", default-features = false } +radroots_identity = { git = "https://github.com/radrootslabs/lib", rev = "be9db78e060ebc0000fa7827ac32efa3f6504f53", version = "=0.1.0-alpha", default-features = false } getrandom = { version = "0.2", default-features = false } hmac = { version = "0.12", default-features = false } quote = { version = "1" } diff --git a/core/compatibility/harvestcircle-ffi-v4.properties b/core/compatibility/harvestcircle-ffi-v4.properties @@ -9,5 +9,5 @@ storage.schema.minimum=5 storage.schema.current=10 product.version=0.1.0-alpha package.version=1.0.0 -source.provenance_digest=db238195b4a5938a8d4d9ac5681c4b125e65c57aa8133ad03e59da4e4bd062bc -source.foundation_baseline=a2038b3e25b9e34f0b8fd001f26a8ed10b5772cb +source.provenance_digest=daded0256c87be5a413358b346498dcecb412d4eff53d2998999d26ec20f3d40 +source.foundation_baseline=c08d18ea569351dddeef70d4c1410708daf067b6 diff --git a/core/crates/harvestcircle_ffi/build.rs b/core/crates/harvestcircle_ffi/build.rs @@ -16,7 +16,7 @@ const CONTRACT_SOURCES: &[&str] = &[ ]; const BASELINE_PATH: &str = "../../compatibility/harvestcircle-ffi-v4.properties"; const PRODUCT_MANIFEST_PATH: &str = "../../../config/product/harvestcircle-v1.properties"; -const SOURCE_PROVENANCE_PATH: &str = concat!("../../provenance/", "stu", "dio-import-v1.toml"); +const SOURCE_PROVENANCE_PATH: &str = "../../provenance/harvestcircle-v1.toml"; const BASELINE_KEYS: &[&str] = &[ "schema", "contract.id", diff --git a/core/crates/harvestcircle_product/src/lib.rs b/core/crates/harvestcircle_product/src/lib.rs @@ -123,13 +123,13 @@ mod tests { let source = std::fs::read_to_string( std::path::Path::new(env!("CARGO_MANIFEST_DIR")) .join("../../provenance") - .join(format!("{}-import-v1.toml", ["stu", "dio"].concat())), + .join("harvestcircle-v1.toml"), ) .expect("read canonical provenance fixture"); let expected = provenance::digest(&source).expect("canonical provenance digest"); assert_eq!( expected, - "db238195b4a5938a8d4d9ac5681c4b125e65c57aa8133ad03e59da4e4bd062bc" + "daded0256c87be5a413358b346498dcecb412d4eff53d2998999d26ec20f3d40" ); assert_eq!( provenance::digest(&source.replace('\n', "\r\n")).unwrap(), diff --git a/core/provenance/harvestcircle-v1.toml b/core/provenance/harvestcircle-v1.toml @@ -0,0 +1,38 @@ +schema = "harvestcircle.source_provenance.v1" +source_product = "HarvestCircle" +source_repository = "https://github.com/radrootslabs/harvestcircle" +foundation_baseline = "c08d18ea569351dddeef70d4c1410708daf067b6" +canonical_radroots_repository = "https://github.com/radrootslabs/lib" +canonical_radroots_revision = "be9db78e060ebc0000fa7827ac32efa3f6504f53" + +[[import]] +component = "domain" +commit = "a4d7deebec3e2ce2c1daa455de6d79857839aed0" + +[[import]] +component = "application" +commit = "97bb016bcf29555bb890a2e3f1effb98890f5502" + +[[import]] +component = "storage" +commit = "249e12d4c6eaaa71e6fd46b0dc330af684af6c40" + +[[import]] +component = "nostr" +commit = "9e68fb26ba41bb74b1bcb6f6f1aebc1ea6361ee3" + +[[import]] +component = "runtime" +commit = "1b8acc061ac044c56c03c60dd9ad69e230cba072" + +[[import]] +component = "ffi" +commit = "d7437d29ed464bbc38d3df300b8b870df0e039ab" + +[[import]] +component = "uniffi_bindgen" +commit = "b3da37d0881d2c54d1a90811b8c001c455d9f9de" + +[[import]] +component = "complete_local_ownership" +commit = "66b603cf145bbb4ade64da622e440daedb4776c8" diff --git a/core/provenance/studio-import-v1.toml b/core/provenance/studio-import-v1.toml @@ -1,38 +0,0 @@ -schema = "harvestcircle.source_provenance.v1" -source_product = "Studio" -source_repository = "https://github.com/radrootslabs/studio_app" -foundation_baseline = "a2038b3e25b9e34f0b8fd001f26a8ed10b5772cb" -canonical_radroots_repository = "https://github.com/radrootslabs/lib" -canonical_radroots_revision = "09065a610d95e57acdc895a14c07580fa099e7c3" - -[[import]] -component = "domain" -commit = "a4d7deebec3e2ce2c1daa455de6d79857839aed0" - -[[import]] -component = "application" -commit = "97bb016bcf29555bb890a2e3f1effb98890f5502" - -[[import]] -component = "storage" -commit = "249e12d4c6eaaa71e6fd46b0dc330af684af6c40" - -[[import]] -component = "nostr" -commit = "9e68fb26ba41bb74b1bcb6f6f1aebc1ea6361ee3" - -[[import]] -component = "runtime" -commit = "1b8acc061ac044c56c03c60dd9ad69e230cba072" - -[[import]] -component = "ffi" -commit = "d7437d29ed464bbc38d3df300b8b870df0e039ab" - -[[import]] -component = "uniffi_bindgen" -commit = "b3da37d0881d2c54d1a90811b8c001c455d9f9de" - -[[import]] -component = "complete_local_ownership" -commit = "66b603cf145bbb4ade64da622e440daedb4776c8" diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml @@ -1278,6 +1278,9 @@ </artifact> </component> <component group="io.opentelemetry" name="opentelemetry-bom" version="1.49.0"> + <artifact name="opentelemetry-bom-1.49.0.module"> + <sha256 value="ed8b705c0f275f3eb6dd4b25a87eadf966bb70459e0db0ba6d54d4d8e76aaf91" origin="Generated by Gradle"/> + </artifact> <artifact name="opentelemetry-bom-1.49.0.pom"> <sha256 value="44fcba73912b950d3ec88129e38e03c181b975f5b5ee73d7bab7866a4b376c50" origin="Generated by Gradle"/> </artifact> @@ -3746,6 +3749,11 @@ <sha256 value="faf0c6538e53ddc0499a63664d8e763c216580b2e18e722ccbdf1b431a6afe26" origin="Generated by Gradle"/> </artifact> </component> + <component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.6.4"> + <artifact name="kotlinx-coroutines-bom-1.6.4.pom"> + <sha256 value="ab2614855fba66aa8a42514dbe3d5a884315ffe1ed63f5932e710a8006245ce1" origin="Generated by Gradle"/> + </artifact> + </component> <component group="org.jetbrains.kotlinx" name="kotlinx-coroutines-bom" version="1.8.0"> <artifact name="kotlinx-coroutines-bom-1.8.0.pom"> <sha256 value="1239e9dbe1397cd5971342956b2511bc3ace7b641842e4372a088dcfa8b9ad55" origin="Generated by Gradle"/> @@ -4050,6 +4058,9 @@ <artifact name="junit-bom-5.10.1.module"> <sha256 value="21b0afcfffe2ecb3770f5eb00ae7a19feaee94e771fa3918173850dae78067b7" origin="Generated by Gradle"/> </artifact> + <artifact name="junit-bom-5.10.1.pom"> + <sha256 value="21c4b0286f4b20069577ff4b20978a85c100ac8a46b6f1c8672fbaab337bc3f2" origin="Generated by Gradle"/> + </artifact> </component> <component group="org.junit" name="junit-bom" version="5.10.2"> <artifact name="junit-bom-5.10.2.pom"> @@ -4067,6 +4078,9 @@ </artifact> </component> <component group="org.junit" name="junit-bom" version="5.13.3"> + <artifact name="junit-bom-5.13.3.module"> + <sha256 value="5dc84d74ef981d023c639eb0cbc4b1f9ef891034287ce1b101effbbc0f1534b4" origin="Generated by Gradle"/> + </artifact> <artifact name="junit-bom-5.13.3.pom"> <sha256 value="e3b93e9bb8871969cf11c0e8ff10f507841db1885ca10578e290846f660fd68e" origin="Generated by Gradle"/> </artifact> diff --git a/radroots.lib.source-lock.v1.toml b/radroots.lib.source-lock.v1.toml @@ -0,0 +1,8 @@ +schema = "radroots.lib.source-lock.v1" +repository = "https://github.com/radrootslabs/lib" +revision = "be9db78e060ebc0000fa7827ac32efa3f6504f53" +architecture = "radroots.crates.release.v2" +workspace_catalog_sha256 = "deca0c080deae187ff8186c0708903e42f41ea57f77c5f91581e23aa561164a4" +version = "0.1.0-alpha" +source_archive_sha256 = "aec2fe198b200f40af81424fbec70a9a8f22b0b38455bc6c81b7eb3be4241748" +lockfile_sha256 = "a2cb8a0f1d252434acba5e417f93ab9cd3be945af554452b3b40e4a8dcea3c80" diff --git a/tools/xtask/src/lib.rs b/tools/xtask/src/lib.rs @@ -235,10 +235,6 @@ fn repo_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<String>) { fn namespace_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<String>) { let legacy = ["stu", "dio"].concat(); - let provenance_path = format!("core/provenance/{legacy}-import-v1.toml"); - let design_provenance_path = "config/design/source_baseline_v1.toml"; - let design_audit_path = "tools/xtask/src/lib.rs"; - let legacy_repository = format!("https://github.com/radrootslabs/{legacy}_app"); let temporary_namespace = ["org", "radroots", "harvestcircle"].join("."); let inherited_preferences = [ ["use", "radroots", "dns"].join("_"), @@ -252,10 +248,7 @@ fn namespace_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<String ]; for path in &inventory.paths { let normalized = path.to_ascii_lowercase(); - if path != &provenance_path - && path != design_provenance_path - && normalized.contains(&legacy) - { + if normalized.contains(&legacy) { findings.push(format!("{path}: legacy product name in source path")); } if normalized.starts_with("app/") @@ -276,69 +269,8 @@ fn namespace_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<String continue; } let source = read_text(root, path); - if path != &provenance_path && path != design_provenance_path { - let mut inspected = source.replace( - if path == "core/Cargo.toml" { - &legacy_repository - } else { - "__no_exact_allowlist__" - }, - "", - ); - if path == "NOTICE" { - inspected = inspected - .replace( - &format!("Radroots {} application work", title_case(&legacy)), - "", - ) - .replace(&provenance_path, ""); - } - for exact in approved_legacy_product_fragments(path, &legacy, &legacy_repository) { - inspected = inspected.replace(&exact, ""); - } - if path == design_audit_path { - inspected = inspected - .replace("design-source-audit", "") - .replace("design_source_audit", "") - .replace("DesignSourceAudit", "") - .replace("design_source_mappings", "") - .replace("push_design_mapping", "") - .replace("current_design_source_baseline_is_exact", "") - .replace( - "design_source_baseline_rejects_snapshot_and_mapping_drift", - "", - ) - .replace(design_provenance_path, "") - .replace("harvestcircle.design_source_baseline.v1", "") - .replace(&format!("source_product = \"{}\"", title_case(&legacy)), "") - .replace(&legacy_repository, "") - .replace( - "shared/src/commonMain/kotlin/com/radroots/studio/ui/shell", - "", - ) - .replace( - "shared/src/commonTest/kotlin/com/radroots/studio/ui/shell", - "", - ) - .replace("shared/src/jvmMain/kotlin/com/radroots/studio/ui/shell", "") - .replace( - "shared/src/commonMain/kotlin/com/radroots/studio/ui/dashboard", - "", - ) - .replace("shared/src/webMain/kotlin/com/radroots/studio/ui/shell", "") - .replace("audit-only/dashboard-visual-inputs", "") - .replace("audit-only/product-copy", "") - .replace("audit-only/web-target", "") - .replace("fixture(\"design-source\")", "") - .replace("com.radroots.studio", "") - .replace(r#"source_product = \"Studio\""#, "") - .replace(&format!("\"{}\"", title_case(&legacy)), ""); - } - if inspected.to_ascii_lowercase().contains(&legacy) { - findings.push(format!( - "{path}: legacy product name outside the exact provenance allowlist" - )); - } + if source.to_ascii_lowercase().contains(&legacy) { + findings.push(format!("{path}: legacy product name in source text")); } if source.contains(&temporary_namespace) || source.contains(&temporary_namespace.replace('.', "/")) @@ -393,27 +325,6 @@ fn namespace_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<String } } -fn approved_legacy_product_fragments( - path: &str, - legacy: &str, - legacy_repository: &str, -) -> Vec<String> { - match path { - "app/shared/src/commonMain/kotlin/org/harvestcircle/product/SurfaceRegistry.kt" => vec![ - format!("round_{legacy}_screen"), - format!("Round{}", title_case(legacy)), - ], - "app/shared/src/commonTest/kotlin/org/harvestcircle/product/SurfaceRegistryTest.kt" => { - vec![format!("round_{legacy}_screen")] - } - "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/FoundationSettingsScreen.kt" => - { - vec![legacy_repository.to_owned()] - } - _ => Vec::new(), - } -} - fn product_shell_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<String>) { let required = [ "app/shared/src/commonMain/kotlin/org/harvestcircle/product/SurfaceRegistry.kt", @@ -831,30 +742,58 @@ fn contains_direct_call(source: &str, call: &str) -> bool { } fn provenance_check(root: &Path, inventory: &Inventory, findings: &mut Vec<String>) { - let legacy = ["stu", "dio"].concat(); - let legacy_repository = format!("https://github.com/radrootslabs/{legacy}_app"); - let provenance_path = format!("core/provenance/{legacy}-import-v1.toml"); + const LIB_REVISION: &str = "be9db78e060ebc0000fa7827ac32efa3f6504f53"; + const PROVENANCE_PATH: &str = "core/provenance/harvestcircle-v1.toml"; + const SOURCE_LOCK_PATH: &str = "radroots.lib.source-lock.v1.toml"; let cargo = read_text(root, "core/Cargo.toml"); - let repository_line = format!("repository = \"{legacy_repository}\""); - if cargo - .lines() - .filter(|line| line.trim() == repository_line) - .count() - != 1 - { - findings.push("core/Cargo.toml: legacy repository allowlist must be exact".to_owned()); + for authority in [ + "repository = \"https://github.com/radrootslabs/harvestcircle\"".to_owned(), + format!( + "radroots_identity = {{ git = \"https://github.com/radrootslabs/lib\", rev = \"{LIB_REVISION}\", version = \"=0.1.0-alpha\", default-features = false }}" + ), + ] { + if cargo + .lines() + .filter(|line| line.trim() == authority) + .count() + != 1 + { + findings.push(format!( + "core/Cargo.toml: missing exact authority: {authority}" + )); + } } - let provenance = read_text(root, &provenance_path); - if !provenance.contains(&format!("source_repository = \"{legacy_repository}\"")) + let provenance = read_text(root, PROVENANCE_PATH); + if !provenance.contains("source_product = \"HarvestCircle\"") || !provenance - .contains("canonical_radroots_revision = \"09065a610d95e57acdc895a14c07580fa099e7c3\"") + .contains("source_repository = \"https://github.com/radrootslabs/harvestcircle\"") + || !provenance.contains(&format!("canonical_radroots_revision = \"{LIB_REVISION}\"")) || !provenance - .contains("foundation_baseline = \"a2038b3e25b9e34f0b8fd001f26a8ed10b5772cb\"") + .contains("foundation_baseline = \"c08d18ea569351dddeef70d4c1410708daf067b6\"") { findings.push(format!( - "{provenance_path}: exact source provenance changed" + "{PROVENANCE_PATH}: exact source provenance changed" )); } + let expected_source_lock = concat!( + "schema = \"radroots.lib.source-lock.v1\"\n", + "repository = \"https://github.com/radrootslabs/lib\"\n", + "revision = \"be9db78e060ebc0000fa7827ac32efa3f6504f53\"\n", + "architecture = \"radroots.crates.release.v2\"\n", + "workspace_catalog_sha256 = \"deca0c080deae187ff8186c0708903e42f41ea57f77c5f91581e23aa561164a4\"\n", + "version = \"0.1.0-alpha\"\n", + "source_archive_sha256 = \"aec2fe198b200f40af81424fbec70a9a8f22b0b38455bc6c81b7eb3be4241748\"\n", + "lockfile_sha256 = \"a2cb8a0f1d252434acba5e417f93ab9cd3be945af554452b3b40e4a8dcea3c80\"\n", + ); + if read_text(root, SOURCE_LOCK_PATH) != expected_source_lock { + findings.push(format!("{SOURCE_LOCK_PATH}: exact Lib source lock changed")); + } + let cargo_lock = read_text(root, "core/Cargo.lock"); + if !cargo_lock.contains(&format!( + "source = \"git+https://github.com/radrootslabs/lib?rev={LIB_REVISION}#{LIB_REVISION}\"" + )) { + findings.push("core/Cargo.lock: selected Lib revision is missing".to_owned()); + } let coordinates = properties(&read_text( root, "config/product/harvestcircle-v1.properties", @@ -893,51 +832,37 @@ fn provenance_check(root: &Path, inventory: &Inventory, findings: &mut Vec<Strin { findings.push("app/shared/build.gradle.kts: shared KMP target boundary changed".to_owned()); } - if !inventory.paths.iter().any(|path| path == &provenance_path) { - findings.push(format!( - "{provenance_path}: source provenance file is missing" - )); + for required in [PROVENANCE_PATH, SOURCE_LOCK_PATH] { + if !inventory.paths.iter().any(|path| path == required) { + findings.push(format!("{required}: governed source evidence is missing")); + } } } fn design_source_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<String>) { - const PATH: &str = "config/design/source_baseline_v1.toml"; + const PATH: &str = "config/design/harvestcircle-v1.toml"; if !inventory.paths.iter().any(|path| path == PATH) { - findings.push(format!("{PATH}: design source baseline is missing")); + findings.push(format!("{PATH}: design contract is missing")); return; } let source = read_text(root, PATH); - let expected_snapshot = "c2fe49f3c3ea43105cb2fff4a67c7cd9c21561c71825440a91654a0c1b12e3b8"; let required_scalars = [ - "schema = \"harvestcircle.design_source_baseline.v1\"", - "source_product = \"Studio\"", - "source_repository = \"https://github.com/radrootslabs/studio_app\"", - "source_head = \"8ae5d8a0377c5673038a20b82b87c314370f0395\"", - "source_state = \"clean\"", - "snapshot_file_count = 102", - "source_license = \"GPL-3.0-only\"", + "schema = \"harvestcircle.design.v1\"", + "repository = \"https://github.com/radrootslabs/harvestcircle\"", + "baseline_revision = \"c08d18ea569351dddeef70d4c1410708daf067b6\"", + "license = \"GPL-3.0-only\"", "golden_host = \"macos-aarch64\"", "golden_status = \"verified\"", + "design_system_root = \"app/design_system\"", + "design_catalog_root = \"tools/design_catalog\"", + "application_shell_root = \"app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell\"", + "golden_test_path = \"app/shared/src/desktopTest/kotlin/org/harvestcircle/ui/shell/HarvestCircleMacGoldenTest.kt\"", ]; for scalar in required_scalars { if source.lines().filter(|line| line.trim() == scalar).count() != 1 { findings.push(format!("{PATH}: missing or duplicate authority: {scalar}")); } } - for (key, digest) in [ - ("snapshot_sha256", expected_snapshot), - ("golden_source_snapshot_sha256", expected_snapshot), - ] { - let expected = format!("{key} = \"{digest}\""); - if source - .lines() - .filter(|line| line.trim() == expected) - .count() - != 1 - { - findings.push(format!("{PATH}: {key} must match the governed snapshot")); - } - } for (path, key, sha256) in [ ( "app/shared/src/desktopTest/resources/goldens/macos-aarch64/design-surface-light.png", @@ -978,79 +903,6 @@ fn design_source_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<St .to_owned(), ); } - let mappings = design_source_mappings(&source, PATH, findings); - let required = [ - ( - "core/designsystem/src/commonMain", - "app/design_system/src/commonMain", - "owned-port", - ), - ( - "core/designsystem/src/commonTest", - "app/design_system/src/commonTest", - "owned-port", - ), - ( - "core/designsystem/src/jvmMain", - "app/design_system/src/desktopMain", - "owned-port", - ), - ( - "tools/designcatalog/src/commonMain", - "tools/design_catalog/src/commonMain", - "owned-port", - ), - ( - "tools/designcatalog/src/jvmMain", - "tools/design_catalog/src/desktopMain", - "owned-port", - ), - ( - "shared/src/commonMain/kotlin/com/radroots/studio/ui/shell", - "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell", - "visual-reference", - ), - ( - "shared/src/commonTest/kotlin/com/radroots/studio/ui/shell", - "app/shared/src/commonTest/kotlin/org/harvestcircle/ui/shell", - "test-reference", - ), - ( - "shared/src/jvmMain/kotlin/com/radroots/studio/ui/shell", - "app/shared/src/desktopMain/kotlin/org/harvestcircle/ui/shell", - "visual-reference", - ), - ( - "shared/src/commonMain/kotlin/com/radroots/studio/ui/dashboard", - "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell", - "visual-reference", - ), - ( - "shared/src/commonMain/composeResources/values/strings.xml", - "audit-only/product-copy", - "reject-product-copy", - ), - ( - "shared/src/webMain/kotlin/com/radroots/studio/ui/shell", - "audit-only/web-target", - "reject-platform-target", - ), - ]; - let expected = required - .iter() - .map(|(source, destination, disposition)| { - ( - (*source).to_owned(), - (*destination).to_owned(), - (*disposition).to_owned(), - ) - }) - .collect::<BTreeSet<_>>(); - if mappings != expected { - findings.push(format!( - "{PATH}: source-to-owned mapping differs from the approved migration" - )); - } let catalog = read_text(root, "gradle/libs.versions.toml"); for required in [ "compose-animation = { module = \"org.jetbrains.compose.animation:animation\", version.ref = \"compose\" }", @@ -1075,11 +927,11 @@ fn design_source_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<St } let lowercase = read_text(root, path).to_ascii_lowercase(); for forbidden in [ - "androidx.compose.material3", - "io.github.kdroidfilter.platformtools", - "com.radroots.studio", + "androidx.compose.material3".to_owned(), + "io.github.kdroidfilter.platformtools".to_owned(), + "com.radroots.".to_owned() + &["stu", "dio"].concat(), ] { - if lowercase.contains(forbidden) { + if lowercase.contains(&forbidden) { findings.push(format!( "{path}: forbidden dependency or legacy namespace: {forbidden}" )); @@ -1120,62 +972,6 @@ fn design_source_audit(root: &Path, inventory: &Inventory, findings: &mut Vec<St } } -fn design_source_mappings( - source: &str, - path: &str, - findings: &mut Vec<String>, -) -> BTreeSet<(String, String, String)> { - let mut mappings = BTreeSet::new(); - let mut current = Vec::new(); - for line in source.lines().map(str::trim) { - if line == "[[mapping]]" { - if !current.is_empty() { - push_design_mapping(&mut mappings, ¤t, path, findings); - current.clear(); - } - } else if !line.is_empty() - && line.contains(" = ") - && (!current.is_empty() || line.starts_with("source = ")) - { - current.push(line.to_owned()); - } - } - if !current.is_empty() { - push_design_mapping(&mut mappings, ¤t, path, findings); - } - mappings -} - -fn push_design_mapping( - mappings: &mut BTreeSet<(String, String, String)>, - lines: &[String], - path: &str, - findings: &mut Vec<String>, -) { - let value = |key: &str| { - lines - .iter() - .find_map(|line| { - line.strip_prefix(&format!("{key} = \"")) - .and_then(|value| value.strip_suffix('"')) - }) - .unwrap_or_default() - .to_owned() - }; - let mapping = (value("source"), value("destination"), value("disposition")); - if mapping.0.is_empty() - || mapping.1.is_empty() - || mapping.2.is_empty() - || mapping.0.starts_with('/') - || mapping.1.starts_with('/') - || mapping.0.split('/').any(|part| part == "..") - || mapping.1.split('/').any(|part| part == "..") - || !mappings.insert(mapping) - { - findings.push(format!("{path}: invalid or duplicate source mapping")); - } -} - fn git_source_policy(root: &Path, findings: &mut Vec<String>) { let deny = read_text(root, "core/deny.toml"); if !deny @@ -1308,13 +1104,6 @@ fn is_lower_hex(value: &str, length: usize) -> bool { .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) } -fn title_case(value: &str) -> String { - let mut characters = value.chars(); - characters.next().map_or_else(String::new, |first| { - first.to_uppercase().collect::<String>() + characters.as_str() - }) -} - fn is_text(relative: &str) -> bool { let name = Path::new(relative) .file_name() @@ -1391,7 +1180,7 @@ mod tests { } #[test] - fn current_design_source_baseline_is_exact() { + fn current_design_contract_is_exact() { let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .parent() .and_then(Path::parent) @@ -1404,12 +1193,25 @@ mod tests { } #[test] - fn design_source_baseline_rejects_snapshot_and_mapping_drift() { - let root = fixture("design-source"); + fn current_source_provenance_and_lib_lock_are_exact() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .expect("repository root") + .to_path_buf(); + let inventory = Inventory::load(&root).expect("source inventory"); + let mut findings = Vec::new(); + provenance_check(&root, &inventory, &mut findings); + assert!(findings.is_empty(), "{findings:#?}"); + } + + #[test] + fn design_contract_rejects_identity_and_root_drift() { + let root = fixture("design-contract"); write( &root, - "config/design/source_baseline_v1.toml", - "schema = \"harvestcircle.design_source_baseline.v1\"\n[[mapping]]\nsource = \"../escape\"\ndestination = \"/tmp\"\ndisposition = \"owned-port\"\n", + "config/design/harvestcircle-v1.toml", + "schema = \"harvestcircle.design.v1\"\nrepository = \"https://example.invalid/other\"\ndesign_system_root = \"../escape\"\n", ); let inventory = Inventory::load(&root).expect("archive inventory"); let mut findings = Vec::new(); @@ -1417,12 +1219,7 @@ mod tests { assert!( findings .iter() - .any(|finding| finding.contains("governed snapshot")) - ); - assert!( - findings - .iter() - .any(|finding| finding.contains("invalid or duplicate source mapping")) + .any(|finding| finding.contains("missing or duplicate authority")) ); fs::remove_dir_all(root).expect("remove fixture"); } @@ -1531,21 +1328,16 @@ mod tests { } #[test] - fn namespace_policy_allows_only_exact_locked_legacy_contracts_and_placeholder() { - let root = fixture("namespace-allowlist"); + fn namespace_policy_rejects_transition_identity_without_exceptions() { + let root = fixture("namespace-no-exceptions"); let legacy = ["stu", "dio"].concat(); let repository = format!("https://github.com/radrootslabs/{legacy}_app"); - let registry = - "app/shared/src/commonMain/kotlin/org/harvestcircle/product/SurfaceRegistry.kt"; let entry = "app/shared/src/commonMain/kotlin/org/harvestcircle/ui/shell/BootstrapIdentityEntry.kt"; write( &root, - registry, - &format!( - "val key = \"round_{legacy}_screen\"\nclass Round{}\n", - title_case(&legacy) - ), + "app/shared/src/commonMain/kotlin/org/harvestcircle/product/SurfaceRegistry.kt", + &format!("val key = \"round_{legacy}_screen\"\n"), ); write(&root, entry, "val placeholder = \"nsec1…\"\n"); write( @@ -1558,7 +1350,20 @@ mod tests { let inventory = Inventory::load(&root).expect("allowlist inventory"); let mut findings = Vec::new(); namespace_audit(&root, &inventory, &mut findings); - assert!(findings.is_empty(), "{findings:?}"); + assert!( + findings + .iter() + .filter(|finding| finding.contains("legacy product name")) + .count() + >= 2, + "{findings:?}" + ); + assert!( + findings + .iter() + .all(|finding| !finding.contains("secret key literal")), + "{findings:?}" + ); fs::remove_dir_all(root).expect("remove fixture"); } @@ -1827,6 +1632,11 @@ mod tests { .iter() .any(|finding| finding.contains("exact source provenance changed")) ); + assert!( + findings + .iter() + .any(|finding| finding.contains("exact Lib source lock changed")) + ); fs::remove_dir_all(root).expect("remove fixture"); }