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:
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");