lib

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

commit d1e4697001aa31b5021c3f0defb012a7417a9e85
parent 08d7134408cd95c9d3fbda1aa60ae4f46682f5e4
Author: triesap <tyson@radroots.org>
Date:   Wed, 22 Jul 2026 19:26:52 +0000

release: make capsule publish policy authoritative

- define capsule-owned crate classification and publish order
- resolve release policy only from the standalone workspace root
- remove ambient parent discovery and environment coupling
- refresh governed source-maintenance artifacts

Diffstat:
MAGENT_INSTRUCTIONS.md | 2+-
Mbuild/nix/common.nix | 2+-
Mcontracts/releases/1.0.0-alpha.1.toml | 7+++++++
Acontracts/releases/publish_policy.toml | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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 | 264++++++++++++++++++++++++++++---------------------------------------------------
8 files changed, 198 insertions(+), 179 deletions(-)

diff --git a/AGENT_INSTRUCTIONS.md b/AGENT_INSTRUCTIONS.md @@ -175,7 +175,7 @@ Validation rules: Release discipline: -- create annotated release tags that match the root release policy at `foundation/contracts/release_runtime/mounted_rust_crates/publish-policy.toml` in the owning monorepo +- create annotated release tags that match the current versioned release contract under `contracts/releases/` - keep repo-owned release commands runnable without depending on GitHub-specific workflow files - when documenting release flow here, document the local repo contract rather than forge-specific orchestration diff --git a/build/nix/common.nix b/build/nix/common.nix @@ -206,7 +206,7 @@ let ''; releasePreflightCommand = '' cargo check -q - env -u RADROOTS_MOUNTED_RUST_CRATE_PUBLISH_POLICY cargo test -q -p xtask + cargo test -q -p xtask cargo run -q -p xtask -- contract validate required_file="$(mktemp)" diff --git a/contracts/releases/1.0.0-alpha.1.toml b/contracts/releases/1.0.0-alpha.1.toml @@ -12,6 +12,13 @@ manifest = "contracts/manifest.toml" operations = "contracts/operations.toml" replica = "contracts/replica.toml" conformance = "contracts/conformance" +publish_policy = "contracts/releases/publish_policy.toml" + +[[changes]] +id = "standalone-release-authority" +classification = "fix" +semver_impacts = ["fix_packaging_metadata"] +summary = "Make the versioned capsule release contract authoritative for crate classification and publication order." [[changes]] id = "calendar-nip52-typestate" diff --git a/contracts/releases/publish_policy.toml b/contracts/releases/publish_policy.toml @@ -0,0 +1,92 @@ +[release] +version = "1.0.0-alpha.1" + +[classification] +public = [ + "radroots_blossom", + "radroots_core", + "radroots_event", + "radroots_event_codec", + "radroots_event_index", + "radroots_geocoder", + "radroots_identity", + "radroots_log", + "radroots_nostr", + "radroots_nostr_accounts", + "radroots_nostr_connect", + "radroots_nostr_signer", + "radroots_protected_store", + "radroots_protocol_contract_v1", + "radroots_replica_schema", + "radroots_replica_store", + "radroots_replica_sync", + "radroots_runtime", + "radroots_runtime_distribution", + "radroots_runtime_manager", + "radroots_runtime_paths", + "radroots_runtime_store", + "radroots_secret_vault", + "radroots_sql_core", + "radroots_trade", +] +internal = [ + "libsqlite3-sys", + "radroots_authority", + "radroots_event_store", + "radroots_mesh", + "radroots_mesh_agent_client", + "radroots_mesh_agent_proto", + "radroots_net", + "radroots_nostr_runtime", + "radroots_nostrdb", + "radroots_outbox", + "radroots_simplex_agent_proto", + "radroots_simplex_agent_runtime", + "radroots_simplex_agent_store", + "radroots_simplex_app_store", + "radroots_simplex_chat_proto", + "radroots_simplex_interop_tests", + "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", +] diff --git a/crates/event_store/contracts/source_maintenance_v1.manifest.json b/crates/event_store/contracts/source_maintenance_v1.manifest.json @@ -250,8 +250,8 @@ { "role": "contract_command_authority", "path": "tools/xtask/src/contract.rs", - "byte_length": 479173, - "sha256": "15b3e754ed6794c8f4c48d7bd316b05ff90578137adb0275e1febdf2891707b4", + "byte_length": 476059, + "sha256": "ae86cbc1bb629869f1fedec00ef1871ffdcbf329e607f31ad817d65de15fcff8", "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 @@ -e8911e6e5710278969cbd15557a5b856b1575dfd11a655711403598370b41221 +9bad6b9f8a88009635289dbc71e7d8c9a86752e1e46b24f767b701fdecf539e9 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\": \"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\": 194875,\n \"sha256\": \"63cc3e6985741e2002ae59f56500bf8d6e0a8246f97a0b8b7f2d2372ef0642e0\",\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\": 834200,\n \"sha256\": \"155374b306f3b30f6095dbfc203150c928b7418e646764011b0401996a636f84\",\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\": 176693,\n \"sha256\": \"88ea58150c49faaad7dde6c9cc89a92ad6b693b112fd55041ac17984bc5c6700\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 479173,\n \"sha256\": \"15b3e754ed6794c8f4c48d7bd316b05ff90578137adb0275e1febdf2891707b4\",\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\": \"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\": 194875,\n \"sha256\": \"63cc3e6985741e2002ae59f56500bf8d6e0a8246f97a0b8b7f2d2372ef0642e0\",\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\": 834200,\n \"sha256\": \"155374b306f3b30f6095dbfc203150c928b7418e646764011b0401996a636f84\",\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\": 176693,\n \"sha256\": \"88ea58150c49faaad7dde6c9cc89a92ad6b693b112fd55041ac17984bc5c6700\",\n \"hash_algorithm\": \"sha256_bytes_v1\"\n },\n {\n \"role\": \"contract_command_authority\",\n \"path\": \"tools/xtask/src/contract.rs\",\n \"byte_length\": 476059,\n \"sha256\": \"ae86cbc1bb629869f1fedec00ef1871ffdcbf329e607f31ad817d65de15fcff8\",\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 = 14216; pub(crate) const SOURCE_MAINTENANCE_MANIFEST_SHA256: &str = - "e8911e6e5710278969cbd15557a5b856b1575dfd11a655711403598370b41221"; + "9bad6b9f8a88009635289dbc71e7d8c9a86752e1e46b24f767b701fdecf539e9"; 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 @@ -40,7 +40,6 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use sha2::{Digest, Sha256}; use std::collections::{BTreeMap, BTreeSet}; -use std::env; use std::fs; use std::path::{Path, PathBuf}; @@ -52,8 +51,6 @@ pub(crate) fn validate_artifact_contracts(workspace_root: &Path) -> Result<(), S validate_knowledge_contract_manifest(workspace_root) } -const ROOT_RELEASE_POLICY_RELATIVE: &str = - "foundation/contracts/release_runtime/mounted_rust_crates/publish-policy.toml"; const CONFORMANCE_ROOT_RELATIVE: &str = "contracts/conformance"; const CONFORMANCE_SCHEMA_RELATIVE: &str = "contracts/conformance/schema/vector.schema.json"; const NIP09_RECONCILIATION_CONFORMANCE_VECTOR_RELATIVE: &str = @@ -80,6 +77,7 @@ const POST_CONFORMANCE_VECTOR_RELATIVE: &str = const FOOD_AVAILABILITY_CONFORMANCE_VECTOR_RELATIVE: &str = "contracts/conformance/vectors/food_availability/profile.v1.json"; const RELEASES_ROOT_RELATIVE: &str = "contracts/releases"; +const RELEASE_POLICY_RELATIVE: &str = "contracts/releases/publish_policy.toml"; const CHANGELOG_RELATIVE: &str = "CHANGELOG.md"; const REPLICA_CONTRACT_RELATIVE: &str = "contracts/replica.toml"; const REPLICA_CONTRACT_NAME: &str = "radroots_replica_contract"; @@ -191,7 +189,6 @@ const KNOWLEDGE_BETA_CONTRACT_IDS: [&str; 3] = [ "radroots.knowledge.change_proposal.v1", "radroots.knowledge.contribution_attestation.v1", ]; -const RELEASE_POLICY_ENV: &str = "RADROOTS_MOUNTED_RUST_CRATE_PUBLISH_POLICY"; const EVENT_BOUNDARY_MATRIX_ENV: &str = "RADROOTS_EVENT_BOUNDARY_MATRIX"; const COVERAGE_REQUIRED_THRESHOLD: f64 = 100.0; const COVERAGE_REQUIRED_THRESHOLD_LABEL: &str = "100/100/100/100"; @@ -1626,6 +1623,7 @@ struct ReleaseRecordArtifacts { operations: String, replica: String, conformance: String, + publish_policy: String, } #[derive(Debug, Deserialize)] @@ -3933,6 +3931,11 @@ fn validate_release_record( CONFORMANCE_ROOT_RELATIVE, true, ), + ( + record.artifacts.publish_policy.as_str(), + RELEASE_POLICY_RELATIVE, + false, + ), ]; for (actual, expected, directory) in expected_artifacts { if actual != expected { @@ -4628,79 +4631,60 @@ fn coverage_root(contract_root: &Path) -> PathBuf { contract_root.to_path_buf() } -#[cfg_attr(not(test), allow(dead_code))] +fn release_contract_path(workspace_root: &Path, _contract_version: &str) -> PathBuf { + workspace_root.join(RELEASE_POLICY_RELATIVE) +} + +#[cfg(test)] fn root_release_policy_path(workspace_root: &Path) -> PathBuf { - workspace_root.join(ROOT_RELEASE_POLICY_RELATIVE) + release_contract_path(workspace_root, "1.0.0") } fn resolve_release_contract_path_with_override( workspace_root: &Path, + contract_version: &str, release_policy_override: Option<PathBuf>, -) -> Result<Option<PathBuf>, String> { +) -> Result<PathBuf, String> { if let Some(path) = release_policy_override { if !path.is_file() { return Err(format!( - "{RELEASE_POLICY_ENV} points to a missing release policy file: {}", + "release policy override points to a missing file: {}", path.display() )); } - return Ok(Some(path)); + return Ok(path); } - for ancestor in workspace_root.ancestors() { - let candidate = ancestor.join(ROOT_RELEASE_POLICY_RELATIVE); - if candidate.is_file() { - return Ok(Some(candidate)); - } + let path = release_contract_path(workspace_root, contract_version); + if !path.is_file() { + return Err(format!( + "release publish policy not found; expected {}", + path.display() + )); } - Ok(None) + Ok(path) } #[cfg(test)] fn load_release_contract( workspace_root: &Path, - contract_root: &Path, + contract_version: &str, ) -> Result<ReleaseContractFile, String> { - load_release_contract_with_override( - workspace_root, - contract_root, - env::var_os(RELEASE_POLICY_ENV).map(PathBuf::from), - ) + load_release_contract_with_override(workspace_root, contract_version, None) } fn load_release_contract_with_override( workspace_root: &Path, - _contract_root: &Path, + contract_version: &str, release_policy_override: Option<PathBuf>, ) -> Result<ReleaseContractFile, String> { - match resolve_release_contract_path_with_override(workspace_root, release_policy_override)? { - Some(path) => parse_toml::<ReleaseContractFile>(&path), - None => load_missing_release_contract(workspace_root), - } -} - -fn missing_release_contract_error() -> String { - format!( - "release publish policy not found; expected {}", - ROOT_RELEASE_POLICY_RELATIVE - ) -} - -#[cfg(not(test))] -fn load_missing_release_contract(_workspace_root: &Path) -> Result<ReleaseContractFile, String> { - Err(missing_release_contract_error()) -} - -#[cfg(test)] -#[cfg_attr(coverage_nightly, coverage(off))] -fn load_missing_release_contract(workspace_root: &Path) -> Result<ReleaseContractFile, String> { - if should_synthesize_owner_contracts_for_tests(workspace_root) { - let raw = synthetic_release_policy_for_workspace(workspace_root)?; - return toml::from_str::<ReleaseContractFile>(&raw) - .map_err(|e| format!("parse synthetic release policy: {e}")); - } - Err(missing_release_contract_error()) + let path = resolve_release_contract_path_with_override( + workspace_root, + contract_version, + release_policy_override, + )?; + parse_toml::<ReleaseContractFile>(&path) } #[cfg(test)] @@ -8055,10 +8039,10 @@ fn publish_config_is_non_public(publish: Option<&PackagePublish>) -> bool { #[cfg(test)] fn validate_release_publish_policy( workspace_root: &Path, - contract_root: &Path, + _contract_root: &Path, contract_version: &str, ) -> Result<(), String> { - let release = load_release_contract(workspace_root, contract_root)?; + let release = load_release_contract(workspace_root, contract_version)?; if release.release.version.trim().is_empty() { return Err("release.version must not be empty".to_string()); } @@ -8240,8 +8224,11 @@ fn validate_release_preflight_with_override_and_profile( release_policy_override.clone(), authority_profile, )?; - let release = - load_release_contract_with_override(workspace_root, &bundle.root, release_policy_override)?; + let release = load_release_contract_with_override( + workspace_root, + bundle.version.contract.version.as_str(), + release_policy_override, + )?; let policy = load_coverage_policy(&bundle.root).expect("validated contract includes coverage policy"); let publish_crates = collect_unique_set( @@ -8338,29 +8325,24 @@ fn validate_contract_bundle_with_release_policy_override_and_profile( validate_core_unit_dimension_variant_order(workspace_root)?; validate_coverage_policy_parity(workspace_root, &bundle.root)?; validate_version_governance(bundle, workspace_root)?; - if resolve_release_contract_path_with_override(workspace_root, release_policy_override.clone()) - .expect("validated release contract path resolution should not fail") - .is_some() - { - validate_release_publish_policy_with_override( - workspace_root, - &bundle.root, - bundle.version.contract.version.as_str(), - release_policy_override, - )?; - } + validate_release_publish_policy_with_override( + workspace_root, + &bundle.root, + bundle.version.contract.version.as_str(), + release_policy_override, + )?; Ok(()) } fn validate_release_publish_policy_with_override( workspace_root: &Path, - contract_root: &Path, + _contract_root: &Path, contract_version: &str, release_policy_override: Option<PathBuf>, ) -> Result<(), String> { let release = load_release_contract_with_override( workspace_root, - contract_root, + contract_version, release_policy_override, )?; if release.release.version.trim().is_empty() { @@ -8512,88 +8494,6 @@ fn validate_release_publish_policy_with_override( Ok(()) } -#[cfg(test)] -#[cfg_attr(coverage_nightly, coverage(off))] -pub fn synthetic_release_policy_for_workspace(workspace_root: &Path) -> Result<String, String> { - let bundle = load_contract_bundle(workspace_root)?; - let publish_configs = workspace_package_publish_configs(workspace_root)?; - let dependencies = read_workspace_package_dependencies(workspace_root)?; - - let mut public = BTreeSet::new(); - let mut internal = BTreeSet::new(); - for (crate_name, publish) in &publish_configs { - if publish_config_is_public(publish.as_ref()) { - public.insert(crate_name.clone()); - } else { - internal.insert(crate_name.clone()); - } - } - - let mut in_degree = BTreeMap::new(); - let mut dependents = BTreeMap::<String, BTreeSet<String>>::new(); - for crate_name in &public { - in_degree.insert(crate_name.clone(), 0usize); - dependents.insert(crate_name.clone(), BTreeSet::new()); - } - for crate_name in &public { - for dep in &dependencies[crate_name] { - if !public.contains(dep) { - continue; - } - *in_degree - .get_mut(crate_name) - .expect("public crate present in indegree map") += 1; - dependents - .get_mut(dep) - .expect("public dependency present in dependents map") - .insert(crate_name.clone()); - } - } - - let mut ready = in_degree - .iter() - .filter(|(_, degree)| **degree == 0) - .map(|(crate_name, _)| crate_name.clone()) - .collect::<BTreeSet<_>>(); - let mut publish_order = Vec::new(); - while let Some(crate_name) = ready.pop_first() { - publish_order.push(crate_name.clone()); - for dependent in dependents[&crate_name].clone() { - let degree = in_degree - .get_mut(&dependent) - .expect("dependent crate present in indegree map"); - *degree -= 1; - if *degree == 0 { - ready.insert(dependent); - } - } - } - if publish_order.len() != public.len() { - return Err("public crate dependency graph contains a cycle".to_string()); - } - - let public = public.into_iter().collect::<Vec<_>>(); - let internal = internal.into_iter().collect::<Vec<_>>(); - Ok(format!( - "[release]\nversion = \"{}\"\n\n[classification]\npublic = {}\ninternal = {}\ndeferred = []\nretired = []\nyank_only = []\n\n[publish_order]\ncrates = {}\n", - bundle.version.contract.version, - toml_inline_array(&public), - toml_inline_array(&internal), - toml_inline_array(&publish_order), - )) -} - -#[cfg(test)] -#[cfg_attr(coverage_nightly, coverage(off))] -fn toml_inline_array(values: &[String]) -> String { - let joined = values - .iter() - .map(|value| format!("\"{value}\"")) - .collect::<Vec<_>>() - .join(", "); - format!("[{joined}]") -} - pub fn load_contract_bundle(workspace_root: &Path) -> Result<ContractBundle, String> { reject_legacy_contract_roots(workspace_root)?; let root = contract_root(workspace_root); @@ -8626,10 +8526,7 @@ fn reject_legacy_contract_roots(workspace_root: &Path) -> Result<(), String> { } pub fn validate_contract_bundle(bundle: &ContractBundle) -> Result<(), String> { - validate_contract_bundle_with_release_policy_override( - bundle, - env::var_os(RELEASE_POLICY_ENV).map(PathBuf::from), - ) + validate_contract_bundle_with_release_policy_override(bundle, None) } #[cfg(test)] @@ -9110,6 +9007,7 @@ manifest = "contracts/manifest.toml" operations = "contracts/operations.toml" replica = "contracts/replica.toml" conformance = "contracts/conformance" +publish_policy = "contracts/releases/publish_policy.toml" [[changes]] id = "synthetic-major-release" @@ -9303,7 +9201,7 @@ vector = "contracts/conformance/vectors/operational_listing/build_draft.v1.json" } fn write_root_release_policy(root: &Path, raw: &str) { - write_file(&root.join(ROOT_RELEASE_POLICY_RELATIVE), raw); + write_file(&root_release_policy_path(root), raw); } fn configure_root_release_policy_workspace(root: &Path) { @@ -11362,15 +11260,18 @@ readme = { workspace = true } let root = create_synthetic_workspace("release_contract_env_override"); let policy_path = root_release_policy_path(&root); let resolved = - resolve_release_contract_path_with_override(&root, Some(policy_path.clone())) + resolve_release_contract_path_with_override(&root, "1.0.0", Some(policy_path.clone())) .expect("existing override policy should resolve"); - assert_eq!(resolved, Some(policy_path)); + assert_eq!(resolved, policy_path); let missing_policy = root.join("missing-release-policy.toml"); - let err = resolve_release_contract_path_with_override(&root, Some(missing_policy.clone())) - .expect_err("missing env policy should fail"); - assert!(err.contains(RELEASE_POLICY_ENV)); - assert!(err.contains("missing release policy file")); + let err = resolve_release_contract_path_with_override( + &root, + "1.0.0", + Some(missing_policy.clone()), + ) + .expect_err("missing fixture policy should fail"); + assert!(err.contains("release policy override points to a missing file")); assert!(err.contains(&missing_policy.display().to_string())); let _ = fs::remove_dir_all(&root); @@ -12658,7 +12559,7 @@ crates = ["radroots_a"] let _ = fs::remove_file(&release_policy_path); let release_load_err = validate_release_publish_policy(&root, &contract_root, "1.0.0") .expect_err("release contract read error"); - assert!(release_load_err.contains(ROOT_RELEASE_POLICY_RELATIVE)); + assert!(release_load_err.contains(RELEASE_POLICY_RELATIVE)); write_file( &release_policy_path, @@ -12746,28 +12647,47 @@ crates = ["radroots_a"] #[test] fn load_release_contract_with_override_reports_override_and_missing_policy_errors() { let root = create_synthetic_workspace("release_contract_loader_errors"); - let contract_root = root.join("contracts"); let missing_override = root.join("missing-release-policy.toml"); - let override_err = load_release_contract_with_override( - &root, - &contract_root, - Some(missing_override.clone()), - ) - .expect_err("missing override should fail"); - assert!(override_err.contains(RELEASE_POLICY_ENV)); - assert!(override_err.contains("missing release policy file")); + let override_err = + load_release_contract_with_override(&root, "1.0.0", Some(missing_override.clone())) + .expect_err("missing override should fail"); + assert!(override_err.contains("release policy override points to a missing file")); let _ = fs::remove_file(root_release_policy_path(&root)); - let missing_policy_err = load_release_contract_with_override(&root, &contract_root, None) + let missing_policy_err = load_release_contract_with_override(&root, "1.0.0", None) .expect_err("missing release policy should fail"); assert!(missing_policy_err.contains("release publish policy not found")); - assert!(missing_policy_err.contains(ROOT_RELEASE_POLICY_RELATIVE)); + assert!(missing_policy_err.contains(RELEASE_POLICY_RELATIVE)); let _ = fs::remove_dir_all(&root); } #[test] + fn release_contract_discovery_does_not_search_parent_directories() { + let parent = temp_root("release_contract_parent_isolation"); + write_file( + &release_contract_path(&parent, "1.0.0"), + "[release]\nversion = \"1.0.0\"\n\n[publish_order]\ncrates = []\n", + ); + let capsule = parent.join("capsule"); + fs::create_dir_all(&capsule).expect("create isolated capsule root"); + + let err = resolve_release_contract_path_with_override(&capsule, "1.0.0", None) + .expect_err("parent release contract must be ignored"); + assert!(err.contains(&capsule.display().to_string())); + assert!( + !err.contains( + &release_contract_path(&parent, "1.0.0") + .display() + .to_string() + ) + ); + + let _ = fs::remove_dir_all(&parent); + } + + #[test] fn root_release_policy_preflight_covers_classification_variants() { let root = create_synthetic_workspace("root_release_policy_classifications"); configure_root_release_policy_workspace(&root); @@ -12897,7 +12817,7 @@ require_conformance_vectors = true let _ = fs::remove_file(root_release_policy_path(&missing_release)); let missing_release_err = validate_generic_release_preflight(&missing_release).expect_err("missing release"); - assert!(missing_release_err.contains(ROOT_RELEASE_POLICY_RELATIVE)); + assert!(missing_release_err.contains(RELEASE_POLICY_RELATIVE)); let _ = fs::remove_dir_all(&missing_release); let missing_required = create_synthetic_workspace("preflight_missing_required");