lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 059927533a174e91d12851eca8c8bc26f4d7eb51
parent 27d111ac59140eb023423a2d0e872650d676de56
Author: triesap <tyson@radroots.org>
Date:   Mon, 27 Jul 2026 07:49:11 +0000

release: define approved public package allowlist

- Bind the exact nineteen-package catalog to the synchronized v1 architecture spec.
- Partition local and external ownership across the unchanged lib and sdk repositories.
- Classify every current workspace package while publication remains frozen.
- Reject catalog drift, unclassified members, and private publication with fixtures.

Diffstat:
Mcontracts/releases/publish_policy.toml | 104+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mcrates/event_store/contracts/source_maintenance_v1.manifest.json | 4++--
Mcrates/event_store/contracts/source_maintenance_v1.manifest.sha256 | 2+-
Mcrates/event_store/src/generated/source_maintenance_manifest.rs | 4++--
Mtools/xtask/src/contract.rs | 434++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
5 files changed, 494 insertions(+), 54 deletions(-)

diff --git a/contracts/releases/publish_policy.toml b/contracts/releases/publish_policy.toml @@ -5,26 +5,70 @@ version = "1.0.0-alpha.1" frozen = true registry = "crates-io" final_enablement_step = 305 +spec_id = "radroots.crates.release.v1" +approved_packages = [ + "radroots-core", + "radroots-identity", + "radroots-blossom", + "radroots-protocol", + "radroots-event", + "radroots-event-codec", + "radroots-trade", + "radroots-signing", + "radroots-transport", + "radroots-nostr", + "radroots-nostr-connect", + "radroots-secrets", + "radroots-storage", + "radroots-storage-sqlite", + "radroots-transport-nostr", + "radroots-sync", + "radroots-geonames", + "radroots-sdk", + "radroots", +] +local_packages = [ + "radroots-core", + "radroots-identity", + "radroots-blossom", + "radroots-protocol", + "radroots-event", + "radroots-event-codec", + "radroots-trade", + "radroots-signing", + "radroots-transport", + "radroots-nostr", + "radroots-nostr-connect", + "radroots-secrets", + "radroots-storage", + "radroots-storage-sqlite", + "radroots-transport-nostr", + "radroots-sync", + "radroots-geonames", +] +external_packages = ["radroots-sdk", "radroots"] -[classification] -public = [ +[workspace_classification] +private = [ + "radroots_authority", "radroots_blossom", "radroots_core", "radroots_event", "radroots_event_codec", "radroots_event_index", + "radroots_event_store", "radroots_geocoder", "radroots_identity", "radroots_log", + "radroots_net", "radroots_nostr", "radroots_nostr_accounts", "radroots_nostr_connect", + "radroots_nostr_runtime", "radroots_nostr_signer", + "radroots_outbox", "radroots_protected_store", "radroots_protocol_contract_v1", - "radroots_replica_schema", - "radroots_replica_store", - "radroots_replica_sync", "radroots_runtime", "radroots_runtime_distribution", "radroots_runtime_manager", @@ -33,17 +77,20 @@ public = [ "radroots_secret_vault", "radroots_sql_core", "radroots_trade", + "radroots_transport", + "radroots_transport_nostr", + "radroots_transport_publish_protocol", ] -internal = [ - "radroots_authority", - "radroots_event_store", +build_codegen = ["xtask"] +test_support = ["radroots_test_fixtures"] +preview = [ "radroots_mesh", "radroots_mesh_agent_client", "radroots_mesh_agent_proto", - "radroots_net", - "radroots_nostr_runtime", "radroots_nostrdb", - "radroots_outbox", + "radroots_replica_schema", + "radroots_replica_store", + "radroots_replica_sync", "radroots_simplex_agent_proto", "radroots_simplex_agent_runtime", "radroots_simplex_agent_store", @@ -53,44 +100,11 @@ internal = [ "radroots_simplex_smp_crypto", "radroots_simplex_smp_proto", "radroots_simplex_smp_transport", - "radroots_test_fixtures", "radroots_trade_sp1_guest", "radroots_trade_sp1_host", - "radroots_transport", - "radroots_transport_nostr", - "radroots_transport_publish_protocol", "radroots_transport_reticulum", - "xtask", ] -deferred = [] retired = [] -yank_only = [] [publish_order] -crates = [ - "radroots_runtime_distribution", - "radroots_replica_schema", - "radroots_protocol_contract_v1", - "radroots_nostr_connect", - "radroots_sql_core", - "radroots_runtime_store", - "radroots_runtime_paths", - "radroots_log", - "radroots_secret_vault", - "radroots_protected_store", - "radroots_event_index", - "radroots_blossom", - "radroots_core", - "radroots_runtime_manager", - "radroots_replica_store", - "radroots_geocoder", - "radroots_event", - "radroots_runtime", - "radroots_identity", - "radroots_event_codec", - "radroots_trade", - "radroots_replica_sync", - "radroots_nostr", - "radroots_nostr_signer", - "radroots_nostr_accounts", -] +crates = [] diff --git a/crates/event_store/contracts/source_maintenance_v1.manifest.json b/crates/event_store/contracts/source_maintenance_v1.manifest.json @@ -257,8 +257,8 @@ { "role": "contract_command_authority", "path": "tools/xtask/src/contract.rs", - "byte_length": 484443, - "sha256": "3ca21a35c0fa412ee8788a7593645b6cb30187daae83e0b7e4af5ced6ca00ed6", + "byte_length": 499430, + "sha256": "866fc167be2e6a618c23ef515fd5e85c64fc60cc6178dca77439dcece64e2505", "hash_algorithm": "sha256_bytes_v1" }, { diff --git a/crates/event_store/contracts/source_maintenance_v1.manifest.sha256 b/crates/event_store/contracts/source_maintenance_v1.manifest.sha256 @@ -1 +1 @@ -e76e00dba774c095c5abdc822bfaa73d287fd45cb1ba990855ce8725d370f619 +45361811f77fffe3882603c13bd67e8cbecc9855d91643e5d4e5edbe90427916 diff --git a/crates/event_store/src/generated/source_maintenance_manifest.rs b/crates/event_store/src/generated/source_maintenance_manifest.rs @@ -1,8 +1,8 @@ // @generated by `cargo xtask contract source-maintenance-manifest --write`; do not edit. -pub(crate) const SOURCE_MAINTENANCE_MANIFEST_JSON: &str = "{\n \"schema_version\": 1,\n \"contract_id\": \"radroots_event_store.source_maintenance_v1\",\n \"hook_id\": \"source_maintenance_v1\",\n \"manifest_schema\": {\n \"path\": \"crates/event_store/contracts/source_maintenance_v1.manifest.schema.json\",\n \"byte_length\": 12315,\n \"sha256\": \"ad4a6c8ae9488fc8033792bc6952af04687f312901c1847d8c668a62913bb642\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"predecessor\": {\n \"hook_id\": \"food_availability_projection_v1\",\n \"manifest\": {\n \"path\": \"crates/event_store/contracts/food_availability_projection_v1.manifest.json\",\n \"byte_length\": 17455,\n \"sha256\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n },\n \"migration\": {\n \"version\": 4,\n \"name\": \"source_maintenance\",\n \"up\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.up.sql\",\n \"byte_length\": 19841,\n \"sha256\": \"425dc799f392b87f265a6fb81f89c4a1c7a5db8391ab0380970708cb9c66704d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"down\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.down.sql\",\n \"byte_length\": 5172,\n \"sha256\": \"fe44fd53c51545c08ea479b385e6781079dab70fc63da2a3c205d727a00ce860\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"schema_sha256\": \"d526d96ea02be12b4b0aed99e97cfdde17c4474ace67111506a7b900ee78b186\",\n \"catalog\": {\n \"objects\": [\n \"radroots_event_store_source_capacity_delete_guard\",\n \"radroots_event_store_source_capacity_insert_guard\",\n \"radroots_event_store_source_capacity_marker_close_guard\",\n \"radroots_event_store_source_capacity_update_guard\",\n \"radroots_event_store_source_capacity_v1\",\n \"radroots_event_store_source_generation_capacity_advance\",\n \"radroots_event_store_source_generation_capacity_guard\"\n ],\n \"replaced_objects\": [\n \"radroots_event_store_food_availability_image_delete_guard\",\n \"radroots_event_store_food_availability_projection_delete_guard\",\n \"radroots_event_store_source_rebuild_marker_insert_guard\"\n ],\n \"tables\": [\n \"radroots_event_store_source_capacity_v1\"\n ],\n \"fts5_tables\": []\n }\n },\n \"source_maintenance\": {\n \"version\": 1,\n \"event_contract_registry_version\": 7,\n \"capacity_authority_id\": \"radroots_event_store_source_capacity_v1\",\n \"accounting\": {\n \"algorithm\": \"sqlite_cast_blob_octet_sum_v1\",\n \"raw_event_columns\": [\n \"event_id\",\n \"pubkey\",\n \"tags_json\",\n \"content\",\n \"sig\",\n \"raw_json\"\n ],\n \"raw_tag_columns\": [\n \"event_id\",\n \"tag_name\",\n \"tag_value\",\n \"tag_json\"\n ],\n \"nullable_raw_tag_columns\": [\n \"tag_value\"\n ]\n },\n \"limits\": {\n \"raw_events\": 25000,\n \"raw_tags\": 250000,\n \"raw_event_text_bytes\": 67108864,\n \"raw_tag_text_bytes\": 33554432,\n \"retained_source_generations\": 8\n },\n \"reopen_validation\": {\n \"mode\": \"bounded_full_raw_recount_v1\",\n \"raw_event_rejection_scan_bound\": 25001,\n \"raw_tag_rejection_scan_bound\": 250001,\n \"generation_history_validation\": \"bounded_count_plus_active_ordinal_v1\",\n \"retained_generation_rejection_scan_bound\": 9\n },\n \"rebuild_seal\": {\n \"nip09_hook_id\": \"nip09_reconciliation_v1\",\n \"nip09_manifest_sha256\": \"74af832420ffbaa9805e89df3c0b34f126a443e1598f757e3372f407f9003b77\",\n \"food_hook_id\": \"food_availability_projection_v1\",\n \"food_manifest_sha256\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\n \"food_scope_fingerprint_sha256\": \"8b63c5ddc48a2cc7db69295238b96d5f814dba50427c80b4d0079f061e6d3de0\",\n \"active_generation_authority\": \"radroots_event_store_source_state\",\n \"marker_close_authority\": \"radroots_event_store_source_capacity_marker_close_guard\"\n }\n },\n \"entry_points\": [\n {\n \"role\": \"migration_registry\",\n \"rust_path\": \"radroots_event_store::migrations::EVENT_STORE_MIGRATIONS[3]\"\n },\n {\n \"role\": \"migration_apply_hook\",\n \"rust_path\": \"radroots_event_store::schema::apply_migration_hook\"\n },\n {\n \"role\": \"migration_validation_hook\",\n \"rust_path\": \"radroots_event_store::schema::validate_migration_hook_state\"\n },\n {\n \"role\": \"capacity_query\",\n \"rust_path\": \"radroots_event_store::RadrootsEventStore::source_capacity_v1\"\n },\n {\n \"role\": \"raw_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_unique_raw_source_append_v1\"\n },\n {\n \"role\": \"raw_append_advance\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::advance_source_capacity_after_insert_v1\"\n },\n {\n \"role\": \"generation_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_source_generation_append_v1\"\n },\n {\n \"role\": \"generation_rebuild_bind\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::bind_source_capacity_to_generation_v1\"\n },\n {\n \"role\": \"sqlite_encoding_preflight\",\n \"rust_path\": \"radroots_event_store::store::validate_main_database_encoding\"\n },\n {\n \"role\": \"source_generation_history_rollback_guard\",\n \"rust_path\": \"radroots_event_store::schema::validate_rollback_preserves_source_generation_history\"\n },\n {\n \"role\": \"result_vector_executor\",\n \"rust_path\": \"source_maintenance_v1_result_vector\"\n }\n ],\n \"source_files\": [\n {\n \"role\": \"workspace_dependency_authority\",\n \"path\": \"Cargo.toml\",\n \"byte_length\": 10725,\n \"sha256\": \"e1975711f349abc7651b29dd1144613865084206eba9f59c92da05ff9dabb6b5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_error_and_limits\",\n \"path\": \"crates/event_store/src/error.rs\",\n \"byte_length\": 19421,\n \"sha256\": \"4772e041cb20a4963afb2f3159804c777e2f2be61bfdb6ee267e7a7c04258972\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"generated_descriptor_registration\",\n \"path\": \"crates/event_store/src/generated.rs\",\n \"byte_length\": 144,\n \"sha256\": \"6b0a8d6f249bd4fc3f878d37cb5e418680f0f1be2d9eec2518dedf03efc47121\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_surface\",\n \"path\": \"crates/event_store/src/lib.rs\",\n \"byte_length\": 3844,\n \"sha256\": \"3cd9653bcb752fb3c4442d4904b98a0a6208011a9a238125b7b7073d7f4e312b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"migration_registry\",\n \"path\": \"crates/event_store/src/migrations.rs\",\n \"byte_length\": 73585,\n \"sha256\": \"a47477d04759ec6e71d14d3d05459864eae78fce0e3c2481a1b03147f04637f7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_model_public_surface\",\n \"path\": \"crates/event_store/src/model.rs\",\n \"byte_length\": 33617,\n \"sha256\": \"79296b8f263aa06d17005795e4515f769f064ea6fd971eeb1296e1151debaf20\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_generation_rebuild_authority\",\n \"path\": \"crates/event_store/src/nip09/reconciliation_v1.rs\",\n \"byte_length\": 184407,\n \"sha256\": \"c455d40fc736e3db264f567c7809af7bd897d89be8a33dfb21667a6ef6b8d6c6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"schema_migration_and_reopen_authority\",\n \"path\": \"crates/event_store/src/schema.rs\",\n \"byte_length\": 146146,\n \"sha256\": \"93b060e80d3edd73f86208e4bf698fa9d53eaf1eeb04526c9261fb8b5726fb0d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_store_and_transaction_authority\",\n \"path\": \"crates/event_store/src/store.rs\",\n \"byte_length\": 394574,\n \"sha256\": \"db57dc3e35e64c7194683142fe55edba853671a829269449dd2273056dfc3a0e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"raw_ingest_capacity_authority\",\n \"path\": \"crates/event_store/src/store/protocol_reconciliation_v1.rs\",\n \"byte_length\": 30140,\n \"sha256\": \"210112eeaa6975a3b4fbb97d5c52588f8c6d8d07975e531d39737fd11235de51\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_runtime\",\n \"path\": \"crates/event_store/src/source_maintenance_v1.rs\",\n \"byte_length\": 51756,\n \"sha256\": \"f8d5b62f0613104aa86658d5bf1baade92c7df83f00ef0cddadd734b9797afca\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"artifact_transaction_authority\",\n \"path\": \"tools/xtask/src/contract/artifact_bundle.rs\",\n \"byte_length\": 38279,\n \"sha256\": \"f326ea57b56d40135f95b6b1e15961f66eed363337180a6d12a5ea903e1a9a29\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_successor_governance\",\n \"path\": \"tools/xtask/src/contract/food_availability_projection.rs\",\n \"byte_length\": 194901,\n \"sha256\": \"01fe9546c95244b4197b3d2a6cec3d72e1f7c48f088bc93f168b5e2e4977b048\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"transitive_predecessor_membership_governance\",\n \"path\": \"tools/xtask/src/contract/nip09_reconciliation.rs\",\n \"byte_length\": 833937,\n \"sha256\": \"0d326a4e459b2026f7ae9fc57a9741def98a2cd1ee6fbdf89713157d964d2547\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_governance\",\n \"path\": \"tools/xtask/src/contract/source_maintenance.rs\",\n \"byte_length\": 176811,\n \"sha256\": \"c22612787364ae417fd4da582130f5fe934820ba0f40f92a47b4eeeb6dffbf16\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 484443,\n \"sha256\": \"3ca21a35c0fa412ee8788a7593645b6cb30187daae83e0b7e4af5ced6ca00ed6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"xtask_dispatch_and_release_preflight\",\n \"path\": \"tools/xtask/src/main.rs\",\n \"byte_length\": 14077,\n \"sha256\": \"d815e65241e47143a51dd87d95e014d975be1d9b94075f3920e4812f41188353\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n ],\n \"public_api\": {\n \"inherited_predecessor_symbols\": [\n \"RADROOTS_ADDRESSABLE_TRANSITION_CURSOR_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_D_TAG_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_FEED_VERSION_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_LIMIT_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_RAW_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_SCAN_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_SCOPE_KIND_MAX_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_APPLY_PAGE_LIMIT_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_VERSION_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_BYTES_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_TERMS_V1\",\n \"RadrootsAddressableTransitionCauseV1\",\n \"RadrootsAddressableTransitionCoordinateV1\",\n \"RadrootsAddressableTransitionCursorV1\",\n \"RadrootsAddressableTransitionEventReferenceV1\",\n \"RadrootsAddressableTransitionOriginV1\",\n \"RadrootsAddressableTransitionPageV1\",\n \"RadrootsAddressableTransitionRawHeadDecisionV1\",\n \"RadrootsAddressableTransitionScopeFingerprintV1\",\n \"RadrootsAddressableTransitionScopeV1\",\n \"RadrootsAddressableTransitionV1\",\n \"RadrootsAddressableTransitionVisibilityV1\",\n \"RadrootsCurrentEventVisibilityV1\",\n \"RadrootsCurrentVisibilityDecisionV1\",\n \"RadrootsFoodAvailabilitySearchQueryV1\",\n \"RadrootsFoodAvailabilityStatusFilterV1\",\n \"RadrootsNip09SuppressionEvidenceV1\",\n \"RadrootsNip09SuppressionOutcome\",\n \"RadrootsNip09SuppressionReason\",\n \"RadrootsStoreProducedCanonicalEventV1\",\n \"RadrootsStoredFoodAvailabilityImageV1\",\n \"RadrootsStoredFoodAvailabilityV1\"\n ],\n \"added_symbols\": [\n \"RADROOTS_EVENT_STORE_RAW_EVENT_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_EVENT_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1\",\n \"RadrootsEventStoreSourceCapacityResourceV1\",\n \"RadrootsEventStoreSourceCapacityV1\"\n ],\n \"methods\": [\n \"RadrootsEventStore::source_capacity_v1\",\n \"RadrootsEventStoreSourceCapacityResourceV1::as_str\",\n \"RadrootsEventStoreSourceCapacityV1::source_generation\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_high_water_seq\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_count\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_limit\"\n ],\n \"error_variants\": [\n \"SourceCapacityExceeded\",\n \"SourceGenerationHistoryLimitReached\",\n \"PersistedEphemeralRawEvent\",\n \"SourceCapacityStateDrift\",\n \"SqliteMainDatabaseEncodingNotUtf8\",\n \"RollbackWouldDiscardSourceGenerationHistory\"\n ],\n \"removed_symbols\": [\n \"RadrootsEventStoreReconciliationResource\",\n \"RadrootsEventStoreError::ReconciliationCapacityExceeded\"\n ],\n \"breaking_replacements\": [\n {\n \"removed\": \"RadrootsEventStoreReconciliationResource\",\n \"replacement\": \"RadrootsEventStoreSourceCapacityResourceV1\"\n },\n {\n \"removed\": \"RadrootsEventStoreError::ReconciliationCapacityExceeded\",\n \"replacement\": \"RadrootsEventStoreError::SourceCapacityExceeded\"\n }\n ]\n },\n \"result_vector\": {\n \"canonical_path\": \"contracts/conformance/vectors/event_store/source_maintenance.v1.json\",\n \"mirror_path\": \"crates/event_store/tests/fixtures/source_maintenance.v1.json\",\n \"byte_length\": 16253,\n \"sha256\": \"997aba2604a2b9d199fb87dc9d07942ca50d91863aeadcf3eeacf16d191dd71f\",\n \"hash_algorithm\": \"sha256_bytes_v1\",\n \"executor_id\": \"radroots_event_store.source_maintenance_v1.result_vector_executor.v1\",\n \"executor_path\": \"crates/event_store/tests/source_maintenance_v1_result_vector.rs\",\n \"executor_test\": \"source_maintenance_v1_result_vector\",\n \"executor_byte_length\": 23510,\n \"executor_sha256\": \"a7487afdfe19fc5fc794811d0f0e6035203e1aabcf0a33a1d398f6b3555d38f3\",\n \"executor_hash_algorithm\": \"sha256_bytes_v1\"\n }\n}\n"; +pub(crate) const SOURCE_MAINTENANCE_MANIFEST_JSON: &str = "{\n \"schema_version\": 1,\n \"contract_id\": \"radroots_event_store.source_maintenance_v1\",\n \"hook_id\": \"source_maintenance_v1\",\n \"manifest_schema\": {\n \"path\": \"crates/event_store/contracts/source_maintenance_v1.manifest.schema.json\",\n \"byte_length\": 12315,\n \"sha256\": \"ad4a6c8ae9488fc8033792bc6952af04687f312901c1847d8c668a62913bb642\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"predecessor\": {\n \"hook_id\": \"food_availability_projection_v1\",\n \"manifest\": {\n \"path\": \"crates/event_store/contracts/food_availability_projection_v1.manifest.json\",\n \"byte_length\": 17455,\n \"sha256\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n },\n \"migration\": {\n \"version\": 4,\n \"name\": \"source_maintenance\",\n \"up\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.up.sql\",\n \"byte_length\": 19841,\n \"sha256\": \"425dc799f392b87f265a6fb81f89c4a1c7a5db8391ab0380970708cb9c66704d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"down\": {\n \"path\": \"crates/event_store/migrations/0004_source_maintenance.down.sql\",\n \"byte_length\": 5172,\n \"sha256\": \"fe44fd53c51545c08ea479b385e6781079dab70fc63da2a3c205d727a00ce860\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n \"schema_sha256\": \"d526d96ea02be12b4b0aed99e97cfdde17c4474ace67111506a7b900ee78b186\",\n \"catalog\": {\n \"objects\": [\n \"radroots_event_store_source_capacity_delete_guard\",\n \"radroots_event_store_source_capacity_insert_guard\",\n \"radroots_event_store_source_capacity_marker_close_guard\",\n \"radroots_event_store_source_capacity_update_guard\",\n \"radroots_event_store_source_capacity_v1\",\n \"radroots_event_store_source_generation_capacity_advance\",\n \"radroots_event_store_source_generation_capacity_guard\"\n ],\n \"replaced_objects\": [\n \"radroots_event_store_food_availability_image_delete_guard\",\n \"radroots_event_store_food_availability_projection_delete_guard\",\n \"radroots_event_store_source_rebuild_marker_insert_guard\"\n ],\n \"tables\": [\n \"radroots_event_store_source_capacity_v1\"\n ],\n \"fts5_tables\": []\n }\n },\n \"source_maintenance\": {\n \"version\": 1,\n \"event_contract_registry_version\": 7,\n \"capacity_authority_id\": \"radroots_event_store_source_capacity_v1\",\n \"accounting\": {\n \"algorithm\": \"sqlite_cast_blob_octet_sum_v1\",\n \"raw_event_columns\": [\n \"event_id\",\n \"pubkey\",\n \"tags_json\",\n \"content\",\n \"sig\",\n \"raw_json\"\n ],\n \"raw_tag_columns\": [\n \"event_id\",\n \"tag_name\",\n \"tag_value\",\n \"tag_json\"\n ],\n \"nullable_raw_tag_columns\": [\n \"tag_value\"\n ]\n },\n \"limits\": {\n \"raw_events\": 25000,\n \"raw_tags\": 250000,\n \"raw_event_text_bytes\": 67108864,\n \"raw_tag_text_bytes\": 33554432,\n \"retained_source_generations\": 8\n },\n \"reopen_validation\": {\n \"mode\": \"bounded_full_raw_recount_v1\",\n \"raw_event_rejection_scan_bound\": 25001,\n \"raw_tag_rejection_scan_bound\": 250001,\n \"generation_history_validation\": \"bounded_count_plus_active_ordinal_v1\",\n \"retained_generation_rejection_scan_bound\": 9\n },\n \"rebuild_seal\": {\n \"nip09_hook_id\": \"nip09_reconciliation_v1\",\n \"nip09_manifest_sha256\": \"74af832420ffbaa9805e89df3c0b34f126a443e1598f757e3372f407f9003b77\",\n \"food_hook_id\": \"food_availability_projection_v1\",\n \"food_manifest_sha256\": \"33b93a3c87ce428e8aa6f5e92643c77203d9aa006c53ce96f3562fe6d68ffd23\",\n \"food_scope_fingerprint_sha256\": \"8b63c5ddc48a2cc7db69295238b96d5f814dba50427c80b4d0079f061e6d3de0\",\n \"active_generation_authority\": \"radroots_event_store_source_state\",\n \"marker_close_authority\": \"radroots_event_store_source_capacity_marker_close_guard\"\n }\n },\n \"entry_points\": [\n {\n \"role\": \"migration_registry\",\n \"rust_path\": \"radroots_event_store::migrations::EVENT_STORE_MIGRATIONS[3]\"\n },\n {\n \"role\": \"migration_apply_hook\",\n \"rust_path\": \"radroots_event_store::schema::apply_migration_hook\"\n },\n {\n \"role\": \"migration_validation_hook\",\n \"rust_path\": \"radroots_event_store::schema::validate_migration_hook_state\"\n },\n {\n \"role\": \"capacity_query\",\n \"rust_path\": \"radroots_event_store::RadrootsEventStore::source_capacity_v1\"\n },\n {\n \"role\": \"raw_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_unique_raw_source_append_v1\"\n },\n {\n \"role\": \"raw_append_advance\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::advance_source_capacity_after_insert_v1\"\n },\n {\n \"role\": \"generation_append_preflight\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::preflight_source_generation_append_v1\"\n },\n {\n \"role\": \"generation_rebuild_bind\",\n \"rust_path\": \"radroots_event_store::source_maintenance_v1::bind_source_capacity_to_generation_v1\"\n },\n {\n \"role\": \"sqlite_encoding_preflight\",\n \"rust_path\": \"radroots_event_store::store::validate_main_database_encoding\"\n },\n {\n \"role\": \"source_generation_history_rollback_guard\",\n \"rust_path\": \"radroots_event_store::schema::validate_rollback_preserves_source_generation_history\"\n },\n {\n \"role\": \"result_vector_executor\",\n \"rust_path\": \"source_maintenance_v1_result_vector\"\n }\n ],\n \"source_files\": [\n {\n \"role\": \"workspace_dependency_authority\",\n \"path\": \"Cargo.toml\",\n \"byte_length\": 10725,\n \"sha256\": \"e1975711f349abc7651b29dd1144613865084206eba9f59c92da05ff9dabb6b5\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"event_store_error_and_limits\",\n \"path\": \"crates/event_store/src/error.rs\",\n \"byte_length\": 19421,\n \"sha256\": \"4772e041cb20a4963afb2f3159804c777e2f2be61bfdb6ee267e7a7c04258972\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"generated_descriptor_registration\",\n \"path\": \"crates/event_store/src/generated.rs\",\n \"byte_length\": 144,\n \"sha256\": \"6b0a8d6f249bd4fc3f878d37cb5e418680f0f1be2d9eec2518dedf03efc47121\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_surface\",\n \"path\": \"crates/event_store/src/lib.rs\",\n \"byte_length\": 3844,\n \"sha256\": \"3cd9653bcb752fb3c4442d4904b98a0a6208011a9a238125b7b7073d7f4e312b\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"migration_registry\",\n \"path\": \"crates/event_store/src/migrations.rs\",\n \"byte_length\": 73585,\n \"sha256\": \"a47477d04759ec6e71d14d3d05459864eae78fce0e3c2481a1b03147f04637f7\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_model_public_surface\",\n \"path\": \"crates/event_store/src/model.rs\",\n \"byte_length\": 33617,\n \"sha256\": \"79296b8f263aa06d17005795e4515f769f064ea6fd971eeb1296e1151debaf20\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_generation_rebuild_authority\",\n \"path\": \"crates/event_store/src/nip09/reconciliation_v1.rs\",\n \"byte_length\": 184407,\n \"sha256\": \"c455d40fc736e3db264f567c7809af7bd897d89be8a33dfb21667a6ef6b8d6c6\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"schema_migration_and_reopen_authority\",\n \"path\": \"crates/event_store/src/schema.rs\",\n \"byte_length\": 146146,\n \"sha256\": \"93b060e80d3edd73f86208e4bf698fa9d53eaf1eeb04526c9261fb8b5726fb0d\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"public_store_and_transaction_authority\",\n \"path\": \"crates/event_store/src/store.rs\",\n \"byte_length\": 394574,\n \"sha256\": \"db57dc3e35e64c7194683142fe55edba853671a829269449dd2273056dfc3a0e\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"raw_ingest_capacity_authority\",\n \"path\": \"crates/event_store/src/store/protocol_reconciliation_v1.rs\",\n \"byte_length\": 30140,\n \"sha256\": \"210112eeaa6975a3b4fbb97d5c52588f8c6d8d07975e531d39737fd11235de51\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_runtime\",\n \"path\": \"crates/event_store/src/source_maintenance_v1.rs\",\n \"byte_length\": 51756,\n \"sha256\": \"f8d5b62f0613104aa86658d5bf1baade92c7df83f00ef0cddadd734b9797afca\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"artifact_transaction_authority\",\n \"path\": \"tools/xtask/src/contract/artifact_bundle.rs\",\n \"byte_length\": 38279,\n \"sha256\": \"f326ea57b56d40135f95b6b1e15961f66eed363337180a6d12a5ea903e1a9a29\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"predecessor_successor_governance\",\n \"path\": \"tools/xtask/src/contract/food_availability_projection.rs\",\n \"byte_length\": 194901,\n \"sha256\": \"01fe9546c95244b4197b3d2a6cec3d72e1f7c48f088bc93f168b5e2e4977b048\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"transitive_predecessor_membership_governance\",\n \"path\": \"tools/xtask/src/contract/nip09_reconciliation.rs\",\n \"byte_length\": 833937,\n \"sha256\": \"0d326a4e459b2026f7ae9fc57a9741def98a2cd1ee6fbdf89713157d964d2547\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"source_maintenance_governance\",\n \"path\": \"tools/xtask/src/contract/source_maintenance.rs\",\n \"byte_length\": 176811,\n \"sha256\": \"c22612787364ae417fd4da582130f5fe934820ba0f40f92a47b4eeeb6dffbf16\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 499430,\n \"sha256\": \"866fc167be2e6a618c23ef515fd5e85c64fc60cc6178dca77439dcece64e2505\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"xtask_dispatch_and_release_preflight\",\n \"path\": \"tools/xtask/src/main.rs\",\n \"byte_length\": 14077,\n \"sha256\": \"d815e65241e47143a51dd87d95e014d975be1d9b94075f3920e4812f41188353\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n }\n ],\n \"public_api\": {\n \"inherited_predecessor_symbols\": [\n \"RADROOTS_ADDRESSABLE_TRANSITION_CURSOR_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_D_TAG_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_FEED_VERSION_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_LIMIT_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_RAW_JSON_MAX_BYTES_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_PAGE_SCAN_MAX_V1\",\n \"RADROOTS_ADDRESSABLE_TRANSITION_SCOPE_KIND_MAX_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_APPLY_PAGE_LIMIT_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_PROJECTION_VERSION_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_BYTES_V1\",\n \"RADROOTS_FOOD_AVAILABILITY_SEARCH_QUERY_MAX_TERMS_V1\",\n \"RadrootsAddressableTransitionCauseV1\",\n \"RadrootsAddressableTransitionCoordinateV1\",\n \"RadrootsAddressableTransitionCursorV1\",\n \"RadrootsAddressableTransitionEventReferenceV1\",\n \"RadrootsAddressableTransitionOriginV1\",\n \"RadrootsAddressableTransitionPageV1\",\n \"RadrootsAddressableTransitionRawHeadDecisionV1\",\n \"RadrootsAddressableTransitionScopeFingerprintV1\",\n \"RadrootsAddressableTransitionScopeV1\",\n \"RadrootsAddressableTransitionV1\",\n \"RadrootsAddressableTransitionVisibilityV1\",\n \"RadrootsCurrentEventVisibilityV1\",\n \"RadrootsCurrentVisibilityDecisionV1\",\n \"RadrootsFoodAvailabilitySearchQueryV1\",\n \"RadrootsFoodAvailabilityStatusFilterV1\",\n \"RadrootsNip09SuppressionEvidenceV1\",\n \"RadrootsNip09SuppressionOutcome\",\n \"RadrootsNip09SuppressionReason\",\n \"RadrootsStoreProducedCanonicalEventV1\",\n \"RadrootsStoredFoodAvailabilityImageV1\",\n \"RadrootsStoredFoodAvailabilityV1\"\n ],\n \"added_symbols\": [\n \"RADROOTS_EVENT_STORE_RAW_EVENT_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_EVENT_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_COUNT_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RAW_TAG_TEXT_BYTES_LIMIT_V1\",\n \"RADROOTS_EVENT_STORE_RETAINED_SOURCE_GENERATION_LIMIT_V1\",\n \"RadrootsEventStoreSourceCapacityResourceV1\",\n \"RadrootsEventStoreSourceCapacityV1\"\n ],\n \"methods\": [\n \"RadrootsEventStore::source_capacity_v1\",\n \"RadrootsEventStoreSourceCapacityResourceV1::as_str\",\n \"RadrootsEventStoreSourceCapacityV1::source_generation\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_count\",\n \"RadrootsEventStoreSourceCapacityV1::raw_event_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_tag_text_bytes\",\n \"RadrootsEventStoreSourceCapacityV1::raw_high_water_seq\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_count\",\n \"RadrootsEventStoreSourceCapacityV1::retained_generation_limit\"\n ],\n \"error_variants\": [\n \"SourceCapacityExceeded\",\n \"SourceGenerationHistoryLimitReached\",\n \"PersistedEphemeralRawEvent\",\n \"SourceCapacityStateDrift\",\n \"SqliteMainDatabaseEncodingNotUtf8\",\n \"RollbackWouldDiscardSourceGenerationHistory\"\n ],\n \"removed_symbols\": [\n \"RadrootsEventStoreReconciliationResource\",\n \"RadrootsEventStoreError::ReconciliationCapacityExceeded\"\n ],\n \"breaking_replacements\": [\n {\n \"removed\": \"RadrootsEventStoreReconciliationResource\",\n \"replacement\": \"RadrootsEventStoreSourceCapacityResourceV1\"\n },\n {\n \"removed\": \"RadrootsEventStoreError::ReconciliationCapacityExceeded\",\n \"replacement\": \"RadrootsEventStoreError::SourceCapacityExceeded\"\n }\n ]\n },\n \"result_vector\": {\n \"canonical_path\": \"contracts/conformance/vectors/event_store/source_maintenance.v1.json\",\n \"mirror_path\": \"crates/event_store/tests/fixtures/source_maintenance.v1.json\",\n \"byte_length\": 16253,\n \"sha256\": \"997aba2604a2b9d199fb87dc9d07942ca50d91863aeadcf3eeacf16d191dd71f\",\n \"hash_algorithm\": \"sha256_bytes_v1\",\n \"executor_id\": \"radroots_event_store.source_maintenance_v1.result_vector_executor.v1\",\n \"executor_path\": \"crates/event_store/tests/source_maintenance_v1_result_vector.rs\",\n \"executor_test\": \"source_maintenance_v1_result_vector\",\n \"executor_byte_length\": 23510,\n \"executor_sha256\": \"a7487afdfe19fc5fc794811d0f0e6035203e1aabcf0a33a1d398f6b3555d38f3\",\n \"executor_hash_algorithm\": \"sha256_bytes_v1\"\n }\n}\n"; pub(crate) const SOURCE_MAINTENANCE_MANIFEST_BYTE_LENGTH: usize = 14459; pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SHA256: &str = - "e76e00dba774c095c5abdc822bfaa73d287fd45cb1ba990855ce8725d370f619"; + "45361811f77fffe3882603c13bd67e8cbecc9855d91643e5d4e5edbe90427916"; pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SCHEMA_VERSION: u32 = 1; pub(crate) const SOURCE_MAINTENANCE_CONTRACT_ID: &str = "radroots_event_store.source_maintenance_v1"; diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs @@ -3079,6 +3079,8 @@ struct ReleaseContractFile { #[serde(default)] publication: Option<PublicationControl>, #[serde(default)] + workspace_classification: Option<WorkspaceReleaseClassification>, + #[serde(default)] classification: ReleaseClassification, #[serde(default)] publish: Option<ReleaseCrateSet>, @@ -3111,6 +3113,52 @@ struct PublicationControl { frozen: bool, registry: String, final_enablement_step: u16, + #[serde(default)] + spec_id: String, + #[serde(default)] + approved_packages: Vec<String>, + #[serde(default)] + local_packages: Vec<String>, + #[serde(default)] + external_packages: Vec<String>, +} + +#[derive(Debug, Deserialize)] +struct WorkspaceReleaseClassification { + #[serde(default)] + private: Vec<String>, + #[serde(default)] + build_codegen: Vec<String>, + #[serde(default)] + test_support: Vec<String>, + #[serde(default)] + preview: Vec<String>, + #[serde(default)] + retired: Vec<String>, +} + +#[derive(Debug, Deserialize)] +struct CratesReleaseArchitecture { + spec_id: String, + package_count: usize, + repositories: CratesReleaseRepositories, + package: Vec<CratesReleasePackage>, +} + +#[derive(Debug, Deserialize)] +struct CratesReleaseRepositories { + lib: CratesReleaseRepository, + sdk: CratesReleaseRepository, +} + +#[derive(Debug, Deserialize)] +struct CratesReleaseRepository { + packages: Vec<String>, +} + +#[derive(Debug, Deserialize)] +struct CratesReleasePackage { + name: String, } #[derive(Debug, Deserialize)] @@ -8192,6 +8240,252 @@ fn validate_publication_control( Ok(true) } +fn validate_v1_release_policy( + workspace_root: &Path, + release: &ReleaseContractFile, + workspace_packages: &BTreeSet<String>, + publish_configs: &BTreeMap<String, Option<PackagePublish>>, + require_v1: bool, +) -> Result<Option<BTreeSet<String>>, String> { + let Some(control) = release.publication.as_ref() else { + if require_v1 { + return Err("publication control is required".to_string()); + } + return Ok(None); + }; + let declares_v1 = !control.spec_id.is_empty() + || !control.approved_packages.is_empty() + || !control.local_packages.is_empty() + || !control.external_packages.is_empty() + || release.workspace_classification.is_some(); + if !declares_v1 { + if require_v1 { + return Err("publication must define the v1 approved package authority".to_string()); + } + return Ok(None); + } + + let architecture_path = workspace_root.join("docs/specs/radroots_crates_release_v1.toml"); + let architecture = parse_toml::<CratesReleaseArchitecture>(&architecture_path)?; + let expected_approved = collect_unique_set( + &architecture + .package + .iter() + .map(|package| package.name.clone()) + .collect::<Vec<_>>(), + "architecture.package.name", + )?; + if architecture.package_count != expected_approved.len() || architecture.package_count != 19 { + return Err(format!( + "release architecture must define exactly 19 unique packages, found package_count {} and {} unique package records", + architecture.package_count, + expected_approved.len() + )); + } + if control.spec_id != architecture.spec_id || control.spec_id != "radroots.crates.release.v1" { + return Err(format!( + "publication.spec_id {} must match architecture id {}", + control.spec_id, architecture.spec_id + )); + } + + let approved = collect_unique_set(&control.approved_packages, "publication.approved_packages")?; + let local = collect_unique_set(&control.local_packages, "publication.local_packages")?; + let external = collect_unique_set(&control.external_packages, "publication.external_packages")?; + let expected_local = collect_unique_set( + &architecture.repositories.lib.packages, + "architecture.repositories.lib.packages", + )?; + let expected_external = collect_unique_set( + &architecture.repositories.sdk.packages, + "architecture.repositories.sdk.packages", + )?; + for (field, actual, expected) in [ + ( + "publication.approved_packages", + &approved, + &expected_approved, + ), + ("publication.local_packages", &local, &expected_local), + ( + "publication.external_packages", + &external, + &expected_external, + ), + ] { + if actual != expected { + let missing = expected + .difference(actual) + .cloned() + .collect::<BTreeSet<_>>(); + let extra = actual + .difference(expected) + .cloned() + .collect::<BTreeSet<_>>(); + return Err(format!( + "{field} is missing approved packages: {}; {field} has unapproved packages: {}", + join_set(&missing), + join_set(&extra) + )); + } + } + let ownership_overlap = local + .intersection(&external) + .cloned() + .collect::<BTreeSet<_>>(); + if !ownership_overlap.is_empty() { + return Err(format!( + "local and external approved package ownership overlaps: {}", + join_set(&ownership_overlap) + )); + } + let mut owned = local.clone(); + owned.extend(external.iter().cloned()); + if owned != approved { + return Err( + "local and external package ownership must partition approved packages".to_string(), + ); + } + let external_in_workspace = external + .intersection(workspace_packages) + .cloned() + .collect::<BTreeSet<_>>(); + if !external_in_workspace.is_empty() { + return Err(format!( + "externally owned approved packages must not be workspace members: {}", + join_set(&external_in_workspace) + )); + } + + let classification = release.workspace_classification.as_ref().ok_or_else(|| { + "workspace_classification is required for the v1 release policy".to_string() + })?; + let private = collect_unique_set(&classification.private, "workspace_classification.private")?; + let build_codegen = collect_unique_set( + &classification.build_codegen, + "workspace_classification.build_codegen", + )?; + let test_support = collect_unique_set( + &classification.test_support, + "workspace_classification.test_support", + )?; + let preview = collect_unique_set(&classification.preview, "workspace_classification.preview")?; + let retired = collect_unique_set(&classification.retired, "workspace_classification.retired")?; + let classes = [ + ("private", &private), + ("build-codegen", &build_codegen), + ("test-support", &test_support), + ("preview", &preview), + ("retired", &retired), + ]; + for index in 0..classes.len() { + for other_index in (index + 1)..classes.len() { + let overlap = classes[index] + .1 + .intersection(classes[other_index].1) + .cloned() + .collect::<BTreeSet<_>>(); + if !overlap.is_empty() { + return Err(format!( + "workspace classification overlap is not allowed between {} and {}: {}", + classes[index].0, + classes[other_index].0, + join_set(&overlap) + )); + } + } + } + let mut classified = BTreeSet::new(); + for (_, entries) in classes { + classified.extend(entries.iter().cloned()); + } + let local_workspace_packages = local + .intersection(workspace_packages) + .cloned() + .collect::<BTreeSet<_>>(); + let public_classification_overlap = classified + .intersection(&local_workspace_packages) + .cloned() + .collect::<BTreeSet<_>>(); + if !public_classification_overlap.is_empty() { + return Err(format!( + "approved local packages must not be classified as private workspace packages: {}", + join_set(&public_classification_overlap) + )); + } + let mut accounted = classified.clone(); + accounted.extend(local_workspace_packages.iter().cloned()); + if accounted != *workspace_packages { + let missing = workspace_packages + .difference(&accounted) + .cloned() + .collect::<BTreeSet<_>>(); + let extra = accounted + .difference(workspace_packages) + .cloned() + .collect::<BTreeSet<_>>(); + return Err(format!( + "workspace classification is missing packages: {}; workspace classification has unknown packages: {}", + join_set(&missing), + join_set(&extra) + )); + } + + if control.registry != "crates-io" { + return Err("publication.registry must be crates-io".to_string()); + } + if control.final_enablement_step != 305 { + return Err("publication.final_enablement_step must be 305".to_string()); + } + let publish_order = collect_unique_set(&release.publish_order.crates, "publish_order.crates")?; + if control.frozen { + if !publish_order.is_empty() { + return Err( + "publish_order.crates must remain empty while publication is frozen".to_string(), + ); + } + for (crate_name, publish) in publish_configs { + if !publish_config_is_non_public(publish.as_ref()) { + return Err(format!( + "publication freeze requires workspace crate {} to set publish = false", + crate_name + )); + } + } + return Ok(Some(BTreeSet::new())); + } + + if local_workspace_packages != local { + let missing = local + .difference(&local_workspace_packages) + .cloned() + .collect::<BTreeSet<_>>(); + return Err(format!( + "publication enablement is missing approved local workspace packages: {}", + join_set(&missing) + )); + } + if publish_order != local { + return Err("publish_order.crates must contain exactly the approved local packages when publication is enabled".to_string()); + } + for (crate_name, publish) in publish_configs { + if local.contains(crate_name) { + if !publish_config_is_public(publish.as_ref()) { + return Err(format!( + "approved local crate {} must set publish = [\"crates-io\"]", + crate_name + )); + } + } else if !publish_config_is_non_public(publish.as_ref()) { + return Err(format!( + "private workspace crate {} must set publish = false", + crate_name + )); + } + } + Ok(Some(local)) +} + #[cfg(test)] fn validate_release_publish_policy( workspace_root: &Path, @@ -8212,6 +8506,19 @@ fn validate_release_publish_policy( let workspace_packages = workspace_package_names(workspace_root)? .into_iter() .collect::<BTreeSet<_>>(); + let publish_configs = workspace_package_publish_configs(workspace_root) + .expect("workspace publish configs are stable"); + if validate_v1_release_policy( + workspace_root, + &release, + &workspace_packages, + &publish_configs, + false, + )? + .is_some() + { + return Ok(()); + } let uses_classification = release.uses_classification(); let public_field = if uses_classification { "classification.public" @@ -8319,8 +8626,6 @@ fn validate_release_publish_policy( } } - let publish_configs = workspace_package_publish_configs(workspace_root) - .expect("workspace publish configs are stable"); if validate_publication_control(&release, &publish_configs, false)? { return Ok(()); } @@ -8493,6 +8798,10 @@ fn validate_contract_bundle_with_release_policy_override_and_profile( authority_profile, OperationAuthorityProfile::CapsuleCanonical ), + matches!( + authority_profile, + OperationAuthorityProfile::CapsuleCanonical + ), )?; Ok(()) } @@ -8510,6 +8819,7 @@ fn validate_release_publish_policy_with_override( contract_version, release_policy_override, true, + false, ) } @@ -8519,6 +8829,7 @@ fn validate_release_publish_policy_with_override_and_control( contract_version: &str, release_policy_override: Option<PathBuf>, require_publication_control: bool, + require_v1_policy: bool, ) -> Result<(), String> { let release = load_release_contract_with_override( workspace_root, @@ -8538,6 +8849,19 @@ fn validate_release_publish_policy_with_override_and_control( let workspace_packages = workspace_package_names(workspace_root)? .into_iter() .collect::<BTreeSet<_>>(); + let publish_configs = workspace_package_publish_configs(workspace_root) + .expect("workspace publish configs are stable"); + if validate_v1_release_policy( + workspace_root, + &release, + &workspace_packages, + &publish_configs, + require_v1_policy, + )? + .is_some() + { + return Ok(()); + } let uses_classification = release.uses_classification(); let public_field = if uses_classification { "classification.public" @@ -8645,8 +8969,6 @@ fn validate_release_publish_policy_with_override_and_control( } } - let publish_configs = workspace_package_publish_configs(workspace_root) - .expect("workspace publish configs are stable"); if validate_publication_control(&release, &publish_configs, require_publication_control)? { return Ok(()); } @@ -11324,6 +11646,7 @@ readme = { workspace = true } version: release.version.clone(), }, publication: None, + workspace_classification: None, classification: ReleaseClassification::default(), publish: Some(ReleaseCrateSet { crates: vec!["radroots_public".to_string()], @@ -11347,6 +11670,7 @@ readme = { workspace = true } version: release.version.clone(), }, publication: None, + workspace_classification: None, classification: ReleaseClassification::default(), publish: None, internal: None, @@ -11363,6 +11687,7 @@ readme = { workspace = true } version: release.version.clone(), }, publication: None, + workspace_classification: None, classification: ReleaseClassification { internal: vec!["radroots_internal_only".to_string()], ..ReleaseClassification::default() @@ -11380,6 +11705,7 @@ readme = { workspace = true } version: release.version.clone(), }, publication: None, + workspace_classification: None, classification: ReleaseClassification { deferred: vec!["radroots_deferred".to_string()], ..ReleaseClassification::default() @@ -11401,6 +11727,7 @@ readme = { workspace = true } version: release.version.clone(), }, publication: None, + workspace_classification: None, classification: ReleaseClassification { retired: vec!["radroots_retired".to_string()], ..ReleaseClassification::default() @@ -11420,6 +11747,7 @@ readme = { workspace = true } let yank_only = ReleaseContractFile { release, publication: None, + workspace_classification: None, classification: ReleaseClassification { yank_only: vec!["radroots_yank_only".to_string()], ..ReleaseClassification::default() @@ -12083,6 +12411,104 @@ crates = ["radroots_a"] } #[test] + fn v1_release_policy_covers_approved_unapproved_unclassified_and_private_fixtures() { + let root = create_synthetic_workspace("v1_release_policy"); + let contract_root = root.join("contracts"); + let release_policy_path = root_release_policy_path(&root); + for member in ["a", "b"] { + write_file( + &root.join("crates").join(member).join("Cargo.toml"), + &format!( + "[package]\nname = \"radroots_{member}\"\nversion = \"1.0.0\"\nedition = \"2024\"\npublish = false\n" + ), + ); + } + + let approved = (1..=19) + .map(|index| format!("package-{index:02}")) + .collect::<Vec<_>>(); + let approved_toml = approved + .iter() + .map(|name| format!("\"{name}\"")) + .collect::<Vec<_>>() + .join(", "); + let external_toml = approved[1..] + .iter() + .map(|name| format!("\"{name}\"")) + .collect::<Vec<_>>() + .join(", "); + let mut architecture = format!( + "spec_id = \"radroots.crates.release.v1\"\npackage_count = 19\n\n[repositories.lib]\npackages = [\"package-01\"]\n\n[repositories.sdk]\npackages = [{external_toml}]\n" + ); + for name in &approved { + architecture.push_str(&format!("\n[[package]]\nname = \"{name}\"\n")); + } + write_file( + &root.join("docs/specs/radroots_crates_release_v1.toml"), + &architecture, + ); + + let policy = |approved_packages: &str, test_support: &str| { + format!( + r#"[release] +version = "1.0.0" + +[publication] +frozen = true +registry = "crates-io" +final_enablement_step = 305 +spec_id = "radroots.crates.release.v1" +approved_packages = [{approved_packages}] +local_packages = ["package-01"] +external_packages = [{external_toml}] + +[workspace_classification] +private = ["radroots_a"] +build_codegen = [] +test_support = [{test_support}] +preview = [] +retired = [] + +[publish_order] +crates = [] +"# + ) + }; + + write_file( + &release_policy_path, + &policy(&approved_toml, "\"radroots_b\""), + ); + validate_release_publish_policy(&root, &contract_root, "1.0.0") + .expect("approved and exhaustively classified fixture must pass"); + + let unapproved = format!("{approved_toml}, \"unapproved-public\""); + write_file(&release_policy_path, &policy(&unapproved, "\"radroots_b\"")); + let unapproved_error = validate_release_publish_policy(&root, &contract_root, "1.0.0") + .expect_err("unapproved public package must fail"); + assert!(unapproved_error.contains("unapproved packages: unapproved-public")); + + write_file(&release_policy_path, &policy(&approved_toml, "")); + let unclassified = validate_release_publish_policy(&root, &contract_root, "1.0.0") + .expect_err("unclassified workspace package must fail"); + assert!(unclassified.contains("workspace classification is missing packages: radroots_b")); + + write_file( + &release_policy_path, + &policy(&approved_toml, "\"radroots_b\""), + ); + write_file( + &root.join("crates/a/Cargo.toml"), + "[package]\nname = \"radroots_a\"\nversion = \"1.0.0\"\nedition = \"2024\"\npublish = [\"crates-io\"]\n", + ); + let private = validate_release_publish_policy(&root, &contract_root, "1.0.0") + .expect_err("private package must remain non-publishable"); + assert!(private.contains("publication freeze requires workspace crate radroots_a")); + + let _ = fs::remove_dir_all(root); + } + + #[test] fn release_preflight_rejects_public_dto_tooling_git_or_path_sources() { let root = create_synthetic_workspace("release_policy_dto_tooling_sources");