contract.rs (523532B)
1 #![forbid(unsafe_code)] 2 3 mod admission_authority; 4 pub(crate) mod artifact_bundle; 5 mod comment_authority; 6 mod deletion_authority; 7 mod registry_v7; 8 pub(crate) use registry_v7::{ 9 validate_event_contract_registry_v7_inventory, write_event_contract_registry_v7_inventory, 10 }; 11 12 use crate::coverage::{CoveragePolicyFile, CoverageThresholds, read_coverage_policy}; 13 use admission_authority::validate_admission_operation_authority; 14 use artifact_bundle::{ 15 GeneratedArtifact, read_regular_file, validate_canonical_json_artifact, 16 validate_sha256_artifact, with_artifact_bundle_transaction, 17 }; 18 use comment_authority::{ 19 COMMENT_CASE_KINDS, COMMENT_CONFORMANCE_VECTOR_RELATIVE, COMMENT_OPERATION_EXPECTATIONS, 20 COMMENT_VECTOR_EXPECTATIONS, REQUIRED_COMMENT_PUBLIC_TYPES, 21 }; 22 use deletion_authority::{ 23 DELETION_ADMIT_INVALID_IDS, DELETION_ADMIT_VALID_IDS, DELETION_AUTHORED_INVALID_IDS, 24 DELETION_AUTHORED_VALID_IDS, DELETION_CASE_KINDS, DELETION_CONFORMANCE_VECTOR_RELATIVE, 25 DELETION_OPERATION_EXPECTATIONS, DELETION_PROJECT_INVALID_IDS, DELETION_PROJECT_VALID_IDS, 26 DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE, DELETION_SUPPRESSION_VALID_IDS, 27 REQUIRED_DELETION_PUBLIC_TYPES, 28 }; 29 use semver::Version; 30 use serde::{Deserialize, Serialize}; 31 use serde_json::Value; 32 use sha2::{Digest, Sha256}; 33 use std::collections::{BTreeMap, BTreeSet}; 34 use std::fs; 35 use std::path::{Path, PathBuf}; 36 use walkdir::WalkDir; 37 38 pub(crate) fn validate_artifact_contracts(workspace_root: &Path) -> Result<(), String> { 39 validate_event_contract_registry_v7_inventory(workspace_root)?; 40 validate_knowledge_contract_manifest(workspace_root) 41 } 42 43 const CONFORMANCE_ROOT_RELATIVE: &str = "contracts/conformance"; 44 const CONFORMANCE_SCHEMA_RELATIVE: &str = "contracts/conformance/schema/vector.schema.json"; 45 const KNOWLEDGE_MANIFEST_RELATIVE: &str = 46 "contracts/knowledge/knowledge_event_contract_manifest.v2.json"; 47 const KNOWLEDGE_MANIFEST_SHA256_RELATIVE: &str = 48 "contracts/knowledge/knowledge_event_contract_manifest.v2.sha256"; 49 const KNOWLEDGE_MANIFEST_WRITE_COMMAND: &str = "cargo xtask contract knowledge-manifest --write"; 50 const KNOWLEDGE_MANIFEST_AND_DECODE_RELATIVE: &str = 51 "contracts/conformance/vectors/knowledge/manifest_and_decode.v1.json"; 52 const KNOWLEDGE_PUBLIC_SURFACE_RELATIVE: &str = 53 "contracts/conformance/vectors/knowledge/public_surface.v1.json"; 54 const POST_CONFORMANCE_VECTOR_RELATIVE: &str = 55 "contracts/conformance/vectors/post/verified_profiles.v1.json"; 56 const FOOD_AVAILABILITY_CONFORMANCE_VECTOR_RELATIVE: &str = 57 "contracts/conformance/vectors/food_availability/profile.v1.json"; 58 const RELEASES_ROOT_RELATIVE: &str = "contracts/releases"; 59 const RELEASE_POLICY_RELATIVE: &str = "contracts/releases/publish_policy.toml"; 60 const SQLITE_RUNTIME_CONTRACT_RELATIVE: &str = "contracts/releases/sqlite_runtime.toml"; 61 const CHANGELOG_RELATIVE: &str = "CHANGELOG.md"; 62 const REPLICA_CONTRACT_RELATIVE: &str = "contracts/replica.toml"; 63 const REPLICA_CONTRACT_NAME: &str = "radroots_replica_contract"; 64 const REPLICA_TRANSFER_CONSTANT: &str = "RADROOTS_REPLICA_TRANSFER_VERSION"; 65 const REPLICA_TRANSFER_VERSION: u32 = 2; 66 const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 19] = [ 67 ( 68 "contracts/conformance/vectors/blossom/bud11_claims.v1.json", 69 "crates/blossom/tests/fixtures/bud11_claims.v1.json", 70 ), 71 ( 72 "contracts/conformance/vectors/blossom/hash_path_and_descriptor.v1.json", 73 "crates/blossom/tests/fixtures/hash_path_and_descriptor.v1.json", 74 ), 75 ( 76 "contracts/conformance/vectors/blossom/bud11_nostr_adapter.v1.json", 77 "crates/nostr/tests/fixtures/bud11_nostr_adapter.v1.json", 78 ), 79 ( 80 "contracts/conformance/vectors/nip17/adapter.v1.json", 81 "crates/nostr/tests/fixtures/nip17_adapter.v1.json", 82 ), 83 ( 84 "contracts/conformance/vectors/calendar/nip52_baseline.v1.json", 85 "crates/event_codec/tests/fixtures/calendar_nip52_baseline.v1.json", 86 ), 87 ( 88 "contracts/conformance/vectors/calendar/radroots_profile.v1.json", 89 "crates/event_codec/tests/fixtures/calendar_radroots_profile.v1.json", 90 ), 91 ( 92 "contracts/conformance/vectors/comment/verified_profile.v1.json", 93 "crates/event_codec/tests/fixtures/comment_verified_profile.v1.json", 94 ), 95 ( 96 "contracts/conformance/vectors/deletion/verified_profile.v1.json", 97 "crates/event_codec/tests/fixtures/deletion_verified_profile.v1.json", 98 ), 99 ( 100 "contracts/conformance/vectors/deletion/suppression.v1.json", 101 "crates/event_codec/tests/fixtures/deletion_suppression.v1.json", 102 ), 103 ( 104 "contracts/conformance/vectors/event/verified_admission.v1.json", 105 "crates/event_codec/tests/fixtures/verified_admission.v1.json", 106 ), 107 ( 108 "contracts/conformance/vectors/event/authored_plan_wire.v1.json", 109 "crates/event_codec/tests/fixtures/authored_plan_wire.v1.json", 110 ), 111 ( 112 "contracts/conformance/vectors/events/operational_listing_tags_full.v1.json", 113 "crates/event_codec/tests/fixtures/operational_listing_tags_full.v1.json", 114 ), 115 ( 116 "contracts/conformance/vectors/food_availability/profile.v1.json", 117 "crates/event_codec/tests/fixtures/food_availability_profile.v1.json", 118 ), 119 ( 120 "contracts/conformance/vectors/operational_listing/build_draft.v1.json", 121 "crates/event_codec/tests/fixtures/operational_listing_build_draft.v1.json", 122 ), 123 ( 124 "contracts/conformance/vectors/operational_listing/build_tags.v1.json", 125 "crates/event_codec/tests/fixtures/operational_listing_build_tags.v1.json", 126 ), 127 ( 128 "contracts/conformance/vectors/operational_listing/parse_event.v1.json", 129 "crates/event_codec/tests/fixtures/operational_listing_parse_event.v1.json", 130 ), 131 ( 132 "contracts/conformance/vectors/profile/metadata.v1.json", 133 "crates/event_codec/tests/fixtures/profile_metadata.v1.json", 134 ), 135 ( 136 "contracts/conformance/vectors/profile/verified_event.v1.json", 137 "crates/event_codec/tests/fixtures/profile_verified_event.v1.json", 138 ), 139 ( 140 "contracts/conformance/vectors/post/verified_profiles.v1.json", 141 "crates/event_codec/tests/fixtures/post_verified_profiles.v1.json", 142 ), 143 ]; 144 const KNOWLEDGE_MVP_SUPPORT_CONTRACT_IDS: [&str; 8] = [ 145 "radroots.wiki.article.v1", 146 "radroots.wiki.redirect.v1", 147 "radroots.wiki.merge_request.v1", 148 "radroots.knowledge.source.v1", 149 "radroots.knowledge.claim.v1", 150 "radroots.knowledge.relation.v1", 151 "radroots.knowledge.review.v1", 152 "radroots.knowledge.field_report.v1", 153 ]; 154 const KNOWLEDGE_BETA_CONTRACT_IDS: [&str; 3] = [ 155 "radroots.knowledge.evidence_bounty.v1", 156 "radroots.knowledge.change_proposal.v1", 157 "radroots.knowledge.contribution_attestation.v1", 158 ]; 159 const EVENT_BOUNDARY_MATRIX_RELATIVE: &str = "contracts/event_boundary_matrix.md"; 160 const COVERAGE_REQUIRED_THRESHOLD: f64 = 90.0; 161 const COVERAGE_REQUIRED_THRESHOLD_LABEL: &str = "90/90/90/90"; 162 const COVERAGE_REPORT_EPSILON: f64 = 0.000_001; 163 const DTO_TOOLING_DEPENDENCIES: [&str; 4] = [ 164 "dto_bindgen", 165 "dto_bindgen_backend_ts", 166 "dto_bindgen_core", 167 "dto_bindgen_macros", 168 ]; 169 const RETIRED_OPERATION_EVENT_NAMES: [&str; 15] = [ 170 "WireEventParts", 171 "RadrootsFrozenEventDraft", 172 "RadrootsNostrEvent", 173 "RadrootsNostrEventRef", 174 "RadrootsNostrEventPtr", 175 "RadrootsCalendarDateEvent", 176 "RadrootsCalendarTimeEvent", 177 "RadrootsCalendarDateValue", 178 "RadrootsInboundCalendarDateEvent", 179 "RadrootsInboundCalendarTimeEvent", 180 "RadrootsCalendar", 181 "RadrootsCalendarEventRsvp", 182 "RadrootsCalendarRsvp", 183 "RadrootsComment", 184 "RadrootsNip10RelayHint", 185 ]; 186 const REQUIRED_CALENDAR_PUBLIC_TYPES: [&str; 34] = [ 187 "Nip01EventWireParts", 188 "BlobUrl", 189 "AuthoredImage", 190 "AuthoredImageError", 191 "IanaTimeZoneId", 192 "CalendarUri", 193 "CalendarRequest", 194 "CalendarParticipant", 195 "CalendarEventError", 196 "CalendarDate", 197 "AuthoredCalendarDateEvent", 198 "AuthoredCalendarTimeEvent", 199 "ParsedNip52CalendarCommon", 200 "ParsedNip52CalendarCommonParts", 201 "ParsedNip52CalendarDateEvent", 202 "ObservedUtcDay", 203 "ParsedNip52CalendarTimeEvent", 204 "CalendarAdmissionError", 205 "AdmittedCalendarDateEvent", 206 "AdmittedCalendarTimeEvent", 207 "CalendarUid", 208 "CalendarEventReference", 209 "CalendarEventRevisionReference", 210 "CalendarEventAuthorReference", 211 "AuthoredCalendar", 212 "ParsedNip52CalendarParts", 213 "ParsedNip52Calendar", 214 "AdmittedCalendar", 215 "CalendarEventRsvpStatus", 216 "CalendarEventFreeBusy", 217 "AuthoredCalendarEventRsvp", 218 "ParsedNip52CalendarEventRsvpParts", 219 "ParsedNip52CalendarEventRsvp", 220 "AdmittedCalendarEventRsvp", 221 ]; 222 const REQUIRED_POST_PUBLIC_TYPES: [&str; 34] = [ 223 "ApprovedBlobUrl", 224 "ByteVerifiedDescriptor", 225 "Nip01EventWireParts", 226 "EventEnvelope", 227 "RadrootsSignatureVerifiedEvent", 228 "AuthoredImage", 229 "Post", 230 "AuthoredPostError", 231 "PostImageDimensions", 232 "AuthoredPostImage", 233 "AuthoredUpdate", 234 "AuthoredPhotoUpdate", 235 "AuthoredAsk", 236 "Nip10ReplyError", 237 "NostrRelayHint", 238 "Nip10ReplyReference", 239 "AuthoredNip10Reply", 240 "RadrootsPostDiagnostic", 241 "RadrootsPostClassification", 242 "RadrootsInboundPostImeta", 243 "RadrootsInboundPostProjection", 244 "RadrootsPostProjectionError", 245 "RadrootsAdmittedRootPostEvent", 246 "RadrootsThreadExcludedPostCandidate", 247 "RadrootsPostAdmissionOutcome", 248 "RadrootsPostAdmissionError", 249 "RadrootsNip10ReplyStyle", 250 "RadrootsNip10ReplyDiagnostic", 251 "RadrootsInboundNip10EventReference", 252 "RadrootsInboundNip10Participant", 253 "RadrootsInboundNip10ReplyProjection", 254 "RadrootsNip10ReplyProjectionError", 255 "RadrootsAdmittedNip10ReplyEvent", 256 "RadrootsNip10ReplyAdmissionError", 257 ]; 258 const REQUIRED_FOOD_AVAILABILITY_PUBLIC_TYPES: [&str; 31] = [ 259 "ByteVerifiedDescriptor", 260 "Nip01EventWireParts", 261 "EventEnvelope", 262 "RadrootsSignatureVerifiedEvent", 263 "ClassifiedListingPartition", 264 "AuthoredImage", 265 "FoodAvailabilityError", 266 "FoodContent", 267 "FoodIdentifier", 268 "FoodText", 269 "FoodPublishedAt", 270 "FoodCurrency", 271 "FoodUnit", 272 "FoodPrice", 273 "FoodQuantity", 274 "FoodAvailabilityStatus", 275 "FoodImageDimensions", 276 "FoodAvailabilityImage", 277 "FoodAvailabilityDetailsParts", 278 "FoodAvailabilityDetails", 279 "RadrootsFoodAvailabilityEncodeError", 280 "RadrootsFoodAvailabilityImageDiagnostic", 281 "RadrootsInboundFoodAvailabilityImage", 282 "RadrootsInboundFoodAvailabilityProjection", 283 "RadrootsFoodAvailabilityProjectionOutcome", 284 "RadrootsFoodAvailabilityProjectionError", 285 "RadrootsAdmittedFoodAvailabilityEvent", 286 "RadrootsExcludedClassifiedListingCandidate", 287 "RadrootsFoodAvailabilityAdmissionOutcome", 288 "RadrootsFoodAvailabilityAdmissionError", 289 "RadrootsFoodAvailabilityRevisionError", 290 ]; 291 const CALENDAR_OPERATION_EXPECTATIONS: [CalendarOperationExpectation; 12] = [ 292 CalendarOperationExpectation { 293 key: "social_calendar_date_event_build_authored_draft", 294 id: "social.calendar_date_event.build_authored_draft", 295 inputs: &["AuthoredCalendarDateEvent"], 296 outputs: &["Nip01EventWireParts"], 297 error_class: "encode_error", 298 rust_modules: &[ 299 "crates/event/src/calendar.rs", 300 "crates/event/src/media.rs", 301 "crates/event_codec/src/calendar/encode.rs", 302 ], 303 rust_types: &[ 304 "radroots_event::calendar::AuthoredCalendarDateEvent", 305 "radroots_event::calendar::CalendarDate", 306 "radroots_event::calendar::CalendarEventError", 307 "radroots_event::media::AuthoredImage", 308 "radroots_event::wire::Nip01EventWireParts", 309 "radroots_event_codec::error::EventEncodeError", 310 ], 311 vector: "contracts/conformance/vectors/calendar/radroots_profile.v1.json", 312 }, 313 CalendarOperationExpectation { 314 key: "social_calendar_date_event_parse_nip52", 315 id: "social.calendar_date_event.parse_nip52", 316 inputs: &["u32", "NostrTags", "String"], 317 outputs: &["ParsedNip52CalendarDateEvent"], 318 error_class: "parse_error", 319 rust_modules: &[ 320 "crates/event/src/calendar.rs", 321 "crates/event_codec/src/calendar/decode.rs", 322 ], 323 rust_types: &[ 324 "radroots_event::calendar::CalendarDate", 325 "radroots_event::calendar::CalendarRequest", 326 "radroots_event::calendar::CalendarUri", 327 "radroots_event::calendar::ParsedNip52CalendarCommon", 328 "radroots_event::calendar::ParsedNip52CalendarCommonParts", 329 "radroots_event::calendar::ParsedNip52CalendarDateEvent", 330 "radroots_event_codec::error::EventParseError", 331 ], 332 vector: "contracts/conformance/vectors/calendar/nip52_baseline.v1.json", 333 }, 334 CalendarOperationExpectation { 335 key: "social_calendar_date_event_admit_radroots_profile", 336 id: "social.calendar_date_event.admit_radroots_profile", 337 inputs: &["ParsedNip52CalendarDateEvent"], 338 outputs: &["AdmittedCalendarDateEvent"], 339 error_class: "admission_error", 340 rust_modules: &[ 341 "crates/event/src/calendar.rs", 342 "crates/event_codec/src/calendar/decode.rs", 343 ], 344 rust_types: &[ 345 "radroots_event::calendar::AdmittedCalendarDateEvent", 346 "radroots_event::calendar::CalendarAdmissionError", 347 "radroots_event::calendar::ParsedNip52CalendarDateEvent", 348 ], 349 vector: "contracts/conformance/vectors/calendar/radroots_profile.v1.json", 350 }, 351 CalendarOperationExpectation { 352 key: "social_calendar_time_event_build_authored_draft", 353 id: "social.calendar_time_event.build_authored_draft", 354 inputs: &["AuthoredCalendarTimeEvent"], 355 outputs: &["Nip01EventWireParts"], 356 error_class: "encode_error", 357 rust_modules: &[ 358 "crates/event/src/calendar.rs", 359 "crates/event/src/media.rs", 360 "crates/event_codec/src/calendar/encode.rs", 361 ], 362 rust_types: &[ 363 "radroots_event::calendar::AuthoredCalendarTimeEvent", 364 "radroots_event::calendar::CalendarEventError", 365 "radroots_event::calendar::IanaTimeZoneId", 366 "radroots_event::media::AuthoredImage", 367 "radroots_event::wire::Nip01EventWireParts", 368 "radroots_event_codec::error::EventEncodeError", 369 ], 370 vector: "contracts/conformance/vectors/calendar/radroots_profile.v1.json", 371 }, 372 CalendarOperationExpectation { 373 key: "social_calendar_time_event_parse_nip52", 374 id: "social.calendar_time_event.parse_nip52", 375 inputs: &["u32", "NostrTags", "String"], 376 outputs: &["ParsedNip52CalendarTimeEvent"], 377 error_class: "parse_error", 378 rust_modules: &[ 379 "crates/event/src/calendar.rs", 380 "crates/event_codec/src/calendar/decode.rs", 381 ], 382 rust_types: &[ 383 "radroots_event::calendar::CalendarRequest", 384 "radroots_event::calendar::CalendarUri", 385 "radroots_event::calendar::IanaTimeZoneId", 386 "radroots_event::calendar::ObservedUtcDay", 387 "radroots_event::calendar::ParsedNip52CalendarCommon", 388 "radroots_event::calendar::ParsedNip52CalendarCommonParts", 389 "radroots_event::calendar::ParsedNip52CalendarTimeEvent", 390 "radroots_event_codec::error::EventParseError", 391 ], 392 vector: "contracts/conformance/vectors/calendar/nip52_baseline.v1.json", 393 }, 394 CalendarOperationExpectation { 395 key: "social_calendar_time_event_admit_radroots_profile", 396 id: "social.calendar_time_event.admit_radroots_profile", 397 inputs: &["ParsedNip52CalendarTimeEvent"], 398 outputs: &["AdmittedCalendarTimeEvent"], 399 error_class: "admission_error", 400 rust_modules: &[ 401 "crates/event/src/calendar.rs", 402 "crates/event_codec/src/calendar/decode.rs", 403 ], 404 rust_types: &[ 405 "radroots_event::calendar::AdmittedCalendarTimeEvent", 406 "radroots_event::calendar::CalendarAdmissionError", 407 "radroots_event::calendar::ParsedNip52CalendarTimeEvent", 408 ], 409 vector: "contracts/conformance/vectors/calendar/radroots_profile.v1.json", 410 }, 411 CalendarOperationExpectation { 412 key: "social_calendar_build_authored_draft", 413 id: "social.calendar.build_authored_draft", 414 inputs: &["AuthoredCalendar"], 415 outputs: &["Nip01EventWireParts"], 416 error_class: "encode_error", 417 rust_modules: &[ 418 "crates/event/src/calendar.rs", 419 "crates/event/src/media.rs", 420 "crates/event_codec/src/calendar/encode.rs", 421 ], 422 rust_types: &[ 423 "radroots_event::calendar::AuthoredCalendar", 424 "radroots_event::calendar::CalendarEventError", 425 "radroots_event::calendar::CalendarEventReference", 426 "radroots_event::calendar::CalendarUid", 427 "radroots_event::media::AuthoredImage", 428 "radroots_event::wire::Nip01EventWireParts", 429 "radroots_event_codec::error::EventEncodeError", 430 ], 431 vector: "contracts/conformance/vectors/calendar/radroots_profile.v1.json", 432 }, 433 CalendarOperationExpectation { 434 key: "social_calendar_parse_nip52", 435 id: "social.calendar.parse_nip52", 436 inputs: &["u32", "NostrTags", "String"], 437 outputs: &["ParsedNip52Calendar"], 438 error_class: "parse_error", 439 rust_modules: &[ 440 "crates/event/src/calendar.rs", 441 "crates/event_codec/src/calendar/decode.rs", 442 ], 443 rust_types: &[ 444 "radroots_event::calendar::CalendarEventError", 445 "radroots_event::calendar::CalendarEventReference", 446 "radroots_event::calendar::CalendarUri", 447 "radroots_event::calendar::ParsedNip52Calendar", 448 "radroots_event::calendar::ParsedNip52CalendarParts", 449 "radroots_event_codec::error::EventParseError", 450 ], 451 vector: "contracts/conformance/vectors/calendar/nip52_baseline.v1.json", 452 }, 453 CalendarOperationExpectation { 454 key: "social_calendar_admit_radroots_profile", 455 id: "social.calendar.admit_radroots_profile", 456 inputs: &["ParsedNip52Calendar"], 457 outputs: &["AdmittedCalendar"], 458 error_class: "admission_error", 459 rust_modules: &[ 460 "crates/event/src/calendar.rs", 461 "crates/event_codec/src/calendar/decode.rs", 462 ], 463 rust_types: &[ 464 "radroots_blossom::BlobUrl", 465 "radroots_event::calendar::AdmittedCalendar", 466 "radroots_event::calendar::CalendarAdmissionError", 467 "radroots_event::calendar::CalendarEventReference", 468 "radroots_event::calendar::CalendarUid", 469 "radroots_event::calendar::ParsedNip52Calendar", 470 ], 471 vector: "contracts/conformance/vectors/calendar/radroots_profile.v1.json", 472 }, 473 CalendarOperationExpectation { 474 key: "social_calendar_rsvp_build_authored_draft", 475 id: "social.calendar_rsvp.build_authored_draft", 476 inputs: &["AuthoredCalendarEventRsvp"], 477 outputs: &["Nip01EventWireParts"], 478 error_class: "encode_error", 479 rust_modules: &[ 480 "crates/event/src/calendar.rs", 481 "crates/event_codec/src/calendar/encode.rs", 482 ], 483 rust_types: &[ 484 "radroots_event::calendar::AuthoredCalendarEventRsvp", 485 "radroots_event::calendar::CalendarEventAuthorReference", 486 "radroots_event::calendar::CalendarEventError", 487 "radroots_event::calendar::CalendarEventReference", 488 "radroots_event::calendar::CalendarEventRevisionReference", 489 "radroots_event::calendar::CalendarUid", 490 "radroots_event::calendar::CalendarEventFreeBusy", 491 "radroots_event::calendar::CalendarEventRsvpStatus", 492 "radroots_event::wire::Nip01EventWireParts", 493 "radroots_event_codec::error::EventEncodeError", 494 ], 495 vector: "contracts/conformance/vectors/calendar/radroots_profile.v1.json", 496 }, 497 CalendarOperationExpectation { 498 key: "social_calendar_rsvp_parse_nip52", 499 id: "social.calendar_rsvp.parse_nip52", 500 inputs: &["u32", "NostrTags", "String"], 501 outputs: &["ParsedNip52CalendarEventRsvp"], 502 error_class: "parse_error", 503 rust_modules: &[ 504 "crates/event/src/calendar.rs", 505 "crates/event_codec/src/calendar/decode.rs", 506 ], 507 rust_types: &[ 508 "radroots_event::calendar::CalendarEventAuthorReference", 509 "radroots_event::calendar::CalendarEventError", 510 "radroots_event::calendar::CalendarEventReference", 511 "radroots_event::calendar::CalendarEventRevisionReference", 512 "radroots_event::calendar::ParsedNip52CalendarEventRsvp", 513 "radroots_event::calendar::ParsedNip52CalendarEventRsvpParts", 514 "radroots_event::calendar::CalendarEventFreeBusy", 515 "radroots_event::calendar::CalendarEventRsvpStatus", 516 "radroots_event_codec::error::EventParseError", 517 ], 518 vector: "contracts/conformance/vectors/calendar/nip52_baseline.v1.json", 519 }, 520 CalendarOperationExpectation { 521 key: "social_calendar_rsvp_admit_radroots_profile", 522 id: "social.calendar_rsvp.admit_radroots_profile", 523 inputs: &["ParsedNip52CalendarEventRsvp"], 524 outputs: &["AdmittedCalendarEventRsvp"], 525 error_class: "admission_error", 526 rust_modules: &[ 527 "crates/event/src/calendar.rs", 528 "crates/event_codec/src/calendar/decode.rs", 529 ], 530 rust_types: &[ 531 "radroots_event::calendar::AdmittedCalendarEventRsvp", 532 "radroots_event::calendar::CalendarAdmissionError", 533 "radroots_event::calendar::CalendarEventAuthorReference", 534 "radroots_event::calendar::CalendarEventReference", 535 "radroots_event::calendar::CalendarEventRevisionReference", 536 "radroots_event::calendar::CalendarUid", 537 "radroots_event::calendar::ParsedNip52CalendarEventRsvp", 538 "radroots_event::calendar::CalendarEventFreeBusy", 539 "radroots_event::calendar::CalendarEventRsvpStatus", 540 ], 541 vector: "contracts/conformance/vectors/calendar/radroots_profile.v1.json", 542 }, 543 ]; 544 const POST_OPERATION_EXPECTATIONS: [PostOperationExpectation; 8] = [ 545 PostOperationExpectation { 546 key: "social_update_build_authored_draft", 547 id: "social.update.build_authored_draft", 548 inputs: &["AuthoredUpdate"], 549 outputs: &["Nip01EventWireParts"], 550 error_class: "encode_error", 551 signing: "none", 552 rust_modules: &[ 553 "crates/event/src/post.rs", 554 "crates/event_codec/src/post/authored.rs", 555 ], 556 rust_types: &[ 557 "radroots_event::post::AuthoredPostError", 558 "radroots_event::post::AuthoredUpdate", 559 ], 560 case_kinds: &[ 561 "social.update.build_authored_draft.valid", 562 "social.update.build_authored_draft.invalid", 563 ], 564 }, 565 PostOperationExpectation { 566 key: "social_photo_update_build_authored_draft", 567 id: "social.photo_update.build_authored_draft", 568 inputs: &["AuthoredPhotoUpdate"], 569 outputs: &["Nip01EventWireParts"], 570 error_class: "encode_error", 571 signing: "none", 572 rust_modules: &[ 573 "crates/event/src/post.rs", 574 "crates/event_codec/src/post/authored.rs", 575 ], 576 rust_types: &[ 577 "radroots_blossom::ApprovedBlobUrl", 578 "radroots_blossom::ByteVerifiedDescriptor", 579 "radroots_event::media::AuthoredImage", 580 "radroots_event::post::AuthoredPhotoUpdate", 581 "radroots_event::post::AuthoredPostError", 582 "radroots_event::post::AuthoredPostImage", 583 "radroots_event::post::PostImageDimensions", 584 ], 585 case_kinds: &[ 586 "social.photo_update.build_authored_draft.valid", 587 "social.photo_update.build_authored_draft.invalid", 588 ], 589 }, 590 PostOperationExpectation { 591 key: "social_ask_build_authored_draft", 592 id: "social.ask.build_authored_draft", 593 inputs: &["AuthoredAsk"], 594 outputs: &["Nip01EventWireParts"], 595 error_class: "encode_error", 596 signing: "none", 597 rust_modules: &[ 598 "crates/event/src/post.rs", 599 "crates/event_codec/src/post/authored.rs", 600 ], 601 rust_types: &[ 602 "radroots_event::post::AuthoredAsk", 603 "radroots_event::post::AuthoredPostError", 604 "radroots_event::post::AuthoredPostImage", 605 ], 606 case_kinds: &[ 607 "social.ask.build_authored_draft.valid", 608 "social.ask.build_authored_draft.invalid", 609 ], 610 }, 611 PostOperationExpectation { 612 key: "social_reply_build_authored_draft", 613 id: "social.reply.build_authored_draft", 614 inputs: &["AuthoredNip10Reply"], 615 outputs: &["Nip01EventWireParts"], 616 error_class: "encode_error", 617 signing: "none", 618 rust_modules: &[ 619 "crates/event/src/relay_hint.rs", 620 "crates/event/src/reply.rs", 621 "crates/event_codec/src/reply/authored.rs", 622 ], 623 rust_types: &[ 624 "radroots_event::post::reply::AuthoredNip10Reply", 625 "radroots_event::post::reply::Nip10ReplyError", 626 "radroots_event::tag::relay_hint::NostrRelayHint", 627 "radroots_event::post::reply::Nip10ReplyReference", 628 ], 629 case_kinds: &[ 630 "social.reply.build_authored_draft.valid", 631 "social.reply.build_authored_draft.invalid", 632 ], 633 }, 634 PostOperationExpectation { 635 key: "social_reply_project_verified_event", 636 id: "social.reply.project_verified_event", 637 inputs: &["RadrootsSignatureVerifiedEvent"], 638 outputs: &["RadrootsInboundNip10ReplyProjection"], 639 error_class: "parse_error", 640 signing: "none", 641 rust_modules: &[ 642 "crates/event/src/relay_hint.rs", 643 "crates/event_codec/src/reply/inbound.rs", 644 ], 645 rust_types: &[ 646 "radroots_event::tag::relay_hint::NostrRelayHint", 647 "radroots_event_codec::decode::reply::RadrootsInboundNip10EventReference", 648 "radroots_event_codec::decode::reply::RadrootsInboundNip10Participant", 649 "radroots_event_codec::decode::reply::RadrootsInboundNip10ReplyProjection", 650 "radroots_event_codec::decode::reply::RadrootsNip10ReplyDiagnostic", 651 "radroots_event_codec::decode::reply::RadrootsNip10ReplyProjectionError", 652 "radroots_event_codec::decode::reply::RadrootsNip10ReplyStyle", 653 "radroots_event_codec::verify::RadrootsSignatureVerifiedEvent", 654 ], 655 case_kinds: &[ 656 "social.reply.project_verified_event.valid", 657 "social.reply.project_verified_event.invalid", 658 ], 659 }, 660 PostOperationExpectation { 661 key: "social_reply_verify_and_admit_event", 662 id: "social.reply.verify_and_admit_event", 663 inputs: &["EventEnvelope"], 664 outputs: &["RadrootsAdmittedNip10ReplyEvent"], 665 error_class: "admission_error", 666 signing: "nip01", 667 rust_modules: &[ 668 "crates/event_codec/src/reply/admission.rs", 669 "crates/event_codec/src/reply/inbound.rs", 670 "crates/event_codec/src/verification.rs", 671 ], 672 rust_types: &[ 673 "radroots_event::envelope::EventEnvelope", 674 "radroots_event_codec::admission::reply::RadrootsAdmittedNip10ReplyEvent", 675 "radroots_event_codec::admission::reply::RadrootsNip10ReplyAdmissionError", 676 "radroots_event_codec::decode::reply::RadrootsInboundNip10ReplyProjection", 677 "radroots_event_codec::verify::RadrootsSignatureVerifiedEvent", 678 ], 679 case_kinds: &[ 680 "social.reply.verify_and_admit_event.valid", 681 "social.reply.verify_and_admit_event.invalid", 682 ], 683 }, 684 PostOperationExpectation { 685 key: "social_post_project_verified_event", 686 id: "social.post.project_verified_event", 687 inputs: &["RadrootsSignatureVerifiedEvent"], 688 outputs: &["RadrootsInboundPostProjection"], 689 error_class: "parse_error", 690 signing: "none", 691 rust_modules: &["crates/event_codec/src/post/inbound.rs"], 692 rust_types: &[ 693 "radroots_event_codec::decode::post::RadrootsInboundPostImeta", 694 "radroots_event_codec::decode::post::RadrootsInboundPostProjection", 695 "radroots_event_codec::decode::post::RadrootsPostClassification", 696 "radroots_event_codec::decode::post::RadrootsPostDiagnostic", 697 "radroots_event_codec::decode::post::RadrootsPostProjectionError", 698 "radroots_event_codec::verify::RadrootsSignatureVerifiedEvent", 699 ], 700 case_kinds: &[ 701 "social.post.project_verified_event.valid", 702 "social.post.project_verified_event.invalid", 703 ], 704 }, 705 PostOperationExpectation { 706 key: "social_post_verify_and_admit_event", 707 id: "social.post.verify_and_admit_event", 708 inputs: &["EventEnvelope"], 709 outputs: &["RadrootsPostAdmissionOutcome"], 710 error_class: "admission_error", 711 signing: "nip01", 712 rust_modules: &[ 713 "crates/event_codec/src/post/admission.rs", 714 "crates/event_codec/src/post/inbound.rs", 715 "crates/event_codec/src/verification.rs", 716 ], 717 rust_types: &[ 718 "radroots_event::envelope::EventEnvelope", 719 "radroots_event_codec::admission::post::RadrootsAdmittedRootPostEvent", 720 "radroots_event_codec::admission::post::RadrootsPostAdmissionError", 721 "radroots_event_codec::admission::post::RadrootsPostAdmissionOutcome", 722 "radroots_event_codec::admission::post::RadrootsThreadExcludedPostCandidate", 723 "radroots_event_codec::decode::post::RadrootsInboundPostProjection", 724 "radroots_event_codec::verify::RadrootsSignatureVerifiedEvent", 725 ], 726 case_kinds: &[ 727 "social.post.verify_and_admit_event.valid", 728 "social.post.verify_and_admit_event.invalid", 729 ], 730 }, 731 ]; 732 const POST_OPERATION_KEY_PREFIXES: [&str; 5] = [ 733 "social_update_", 734 "social_photo_update_", 735 "social_ask_", 736 "social_reply_", 737 "social_post_", 738 ]; 739 const POST_OPERATION_ID_PREFIXES: [&str; 5] = [ 740 "social.update.", 741 "social.photo_update.", 742 "social.ask.", 743 "social.reply.", 744 "social.post.", 745 ]; 746 const POST_VECTOR_EXPECTATIONS: [(&str, &str); 64] = [ 747 ( 748 "authored_update_wire", 749 "social.update.build_authored_draft.valid", 750 ), 751 ( 752 "authored_update_blank", 753 "social.update.build_authored_draft.invalid", 754 ), 755 ( 756 "authored_photo_update_wire", 757 "social.photo_update.build_authored_draft.valid", 758 ), 759 ( 760 "authored_photo_update_mime_underscore", 761 "social.photo_update.build_authored_draft.invalid", 762 ), 763 ("authored_ask_wire", "social.ask.build_authored_draft.valid"), 764 ( 765 "authored_ask_blank", 766 "social.ask.build_authored_draft.invalid", 767 ), 768 ( 769 "authored_nip10_direct_wire", 770 "social.reply.build_authored_draft.valid", 771 ), 772 ( 773 "authored_nip10_nested_wire", 774 "social.reply.build_authored_draft.valid", 775 ), 776 ( 777 "authored_nip10_canonical_ipv6_relay", 778 "social.reply.build_authored_draft.valid", 779 ), 780 ( 781 "authored_nip10_ambiguous_parent", 782 "social.reply.build_authored_draft.invalid", 783 ), 784 ( 785 "authored_nip10_invalid_event_id", 786 "social.reply.build_authored_draft.invalid", 787 ), 788 ( 789 "authored_nip10_invalid_relay_percent_host", 790 "social.reply.build_authored_draft.invalid", 791 ), 792 ( 793 "authored_nip10_invalid_relay_bad_percent_host", 794 "social.reply.build_authored_draft.invalid", 795 ), 796 ( 797 "authored_nip10_invalid_relay_ipv4_overflow", 798 "social.reply.build_authored_draft.invalid", 799 ), 800 ( 801 "authored_nip10_invalid_relay_ipvfuture", 802 "social.reply.build_authored_draft.invalid", 803 ), 804 ( 805 "authored_nip10_invalid_relay_empty_port", 806 "social.reply.build_authored_draft.invalid", 807 ), 808 ( 809 "authored_nip10_invalid_relay_zero_port", 810 "social.reply.build_authored_draft.invalid", 811 ), 812 ( 813 "authored_nip10_invalid_relay_port_overflow", 814 "social.reply.build_authored_draft.invalid", 815 ), 816 ( 817 "project_signed_nip10_marked_direct", 818 "social.reply.project_verified_event.valid", 819 ), 820 ( 821 "project_signed_nip10_marked_with_citation", 822 "social.reply.project_verified_event.valid", 823 ), 824 ( 825 "project_signed_nip10_marked_with_malformed_citation", 826 "social.reply.project_verified_event.valid", 827 ), 828 ( 829 "project_signed_nip10_marked_nested_reordered", 830 "social.reply.project_verified_event.valid", 831 ), 832 ( 833 "project_signed_nip10_positional_direct", 834 "social.reply.project_verified_event.valid", 835 ), 836 ( 837 "project_signed_nip10_positional_direct_with_author_hint", 838 "social.reply.project_verified_event.valid", 839 ), 840 ( 841 "project_signed_nip10_positional_many", 842 "social.reply.project_verified_event.valid", 843 ), 844 ( 845 "project_signed_nip10_positional_many_with_author_hints", 846 "social.reply.project_verified_event.valid", 847 ), 848 ( 849 "project_signed_nip10_positional_malformed_middle_citation", 850 "social.reply.project_verified_event.valid", 851 ), 852 ( 853 "project_signed_nip10_precedes_ask_and_media", 854 "social.reply.project_verified_event.valid", 855 ), 856 ( 857 "project_signed_nip10_invalid_relay", 858 "social.reply.project_verified_event.valid", 859 ), 860 ( 861 "project_signed_nip10_canonical_relay_authorities", 862 "social.reply.project_verified_event.valid", 863 ), 864 ( 865 "project_signed_nip10_malformed_relay_authorities", 866 "social.reply.project_verified_event.valid", 867 ), 868 ( 869 "project_signed_nip10_ambiguous_same_reference", 870 "social.reply.project_verified_event.invalid", 871 ), 872 ( 873 "project_signed_nip10_missing_author", 874 "social.reply.project_verified_event.valid", 875 ), 876 ( 877 "project_signed_nip10_invalid_event_id", 878 "social.reply.project_verified_event.invalid", 879 ), 880 ( 881 "project_signed_nip10_author_hint_mismatch", 882 "social.reply.project_verified_event.valid", 883 ), 884 ( 885 "project_signed_nip10_blank_content", 886 "social.reply.project_verified_event.valid", 887 ), 888 ( 889 "project_signed_nip10_invalid_author_hint_tolerated", 890 "social.reply.project_verified_event.valid", 891 ), 892 ( 893 "project_signed_nip10_invalid_participant_tolerated", 894 "social.reply.project_verified_event.valid", 895 ), 896 ( 897 "project_signed_nip10_lone_reply_marker", 898 "social.reply.project_verified_event.invalid", 899 ), 900 ( 901 "project_signed_nip10_unknown_marker", 902 "social.reply.project_verified_event.invalid", 903 ), 904 ( 905 "admit_signed_nip10_marked_direct", 906 "social.reply.verify_and_admit_event.valid", 907 ), 908 ( 909 "admit_signed_nip10_positional_direct", 910 "social.reply.verify_and_admit_event.valid", 911 ), 912 ( 913 "admit_signed_nip10_invalid_signature", 914 "social.reply.verify_and_admit_event.invalid", 915 ), 916 ( 917 "project_signed_update", 918 "social.post.project_verified_event.valid", 919 ), 920 ( 921 "project_signed_empty_inbound_update", 922 "social.post.project_verified_event.valid", 923 ), 924 ( 925 "project_signed_structural_photo", 926 "social.post.project_verified_event.valid", 927 ), 928 ( 929 "project_signed_photo_preserves_fallbacks_and_unknown_fields", 930 "social.post.project_verified_event.valid", 931 ), 932 ( 933 "project_signed_normalized_ask_precedes_malformed_media", 934 "social.post.project_verified_event.valid", 935 ), 936 ( 937 "project_signed_malformed_imeta_is_update", 938 "social.post.project_verified_event.valid", 939 ), 940 ( 941 "project_signed_duplicate_singleton_is_update", 942 "social.post.project_verified_event.valid", 943 ), 944 ( 945 "project_signed_mixed_imeta_is_update", 946 "social.post.project_verified_event.valid", 947 ), 948 ( 949 "project_signed_thread_candidate_precedes_ask_and_media", 950 "social.post.project_verified_event.valid", 951 ), 952 ( 953 "project_signed_malformed_ask_marker_is_update", 954 "social.post.project_verified_event.valid", 955 ), 956 ( 957 "project_signed_duplicate_normalized_ask_marker", 958 "social.post.project_verified_event.invalid", 959 ), 960 ( 961 "project_signed_kind_20_is_not_photo_update", 962 "social.post.project_verified_event.invalid", 963 ), 964 ( 965 "admit_signed_update", 966 "social.post.verify_and_admit_event.valid", 967 ), 968 ( 969 "admit_signed_structural_photo", 970 "social.post.verify_and_admit_event.valid", 971 ), 972 ( 973 "admit_signed_normalized_ask_precedes_malformed_media", 974 "social.post.verify_and_admit_event.valid", 975 ), 976 ( 977 "admit_signed_thread_candidate_precedes_ask_and_media", 978 "social.post.verify_and_admit_event.valid", 979 ), 980 ( 981 "admit_signed_empty_e_thread_candidate", 982 "social.post.verify_and_admit_event.valid", 983 ), 984 ( 985 "admit_signed_empty_e_value_thread_candidate", 986 "social.post.verify_and_admit_event.valid", 987 ), 988 ( 989 "admit_signed_duplicate_normalized_ask_marker", 990 "social.post.verify_and_admit_event.invalid", 991 ), 992 ( 993 "admit_signed_kind_20_is_not_photo_update", 994 "social.post.verify_and_admit_event.invalid", 995 ), 996 ( 997 "admit_signed_invalid_signature", 998 "social.post.verify_and_admit_event.invalid", 999 ), 1000 ]; 1001 const FOOD_AVAILABILITY_OPERATION_EXPECTATIONS: [FoodAvailabilityOperationExpectation; 4] = [ 1002 FoodAvailabilityOperationExpectation { 1003 key: "food_availability_build_authored_draft", 1004 id: "food_availability.build_authored_draft", 1005 inputs: &["FoodAvailabilityDetails", "u64"], 1006 outputs: &["Nip01EventWireParts"], 1007 error_class: "encode_error", 1008 signing: "none", 1009 rust_modules: &[ 1010 "crates/event/src/food_availability.rs", 1011 "crates/event_codec/src/food_availability/authored.rs", 1012 ], 1013 rust_types: &[ 1014 "radroots_blossom::ByteVerifiedDescriptor", 1015 "radroots_event::food::availability::FoodAvailabilityDetails", 1016 "radroots_event::food::availability::FoodAvailabilityDetailsParts", 1017 "radroots_event::food::availability::FoodAvailabilityError", 1018 "radroots_event::food::availability::FoodAvailabilityImage", 1019 "radroots_event::media::AuthoredImage", 1020 "radroots_event::wire::Nip01EventWireParts", 1021 "radroots_event_codec::food_availability::authored::RadrootsFoodAvailabilityEncodeError", 1022 ], 1023 case_kinds: &[ 1024 "food_availability.build_authored_draft.valid", 1025 "food_availability.build_authored_draft.invalid", 1026 ], 1027 }, 1028 FoodAvailabilityOperationExpectation { 1029 key: "food_availability_project_verified_event", 1030 id: "food_availability.project_verified_event", 1031 inputs: &["RadrootsSignatureVerifiedEvent"], 1032 outputs: &["RadrootsFoodAvailabilityProjectionOutcome"], 1033 error_class: "parse_error", 1034 signing: "none", 1035 rust_modules: &[ 1036 "crates/event/src/classified_listing.rs", 1037 "crates/event/src/food_availability.rs", 1038 "crates/event_codec/src/food_availability/inbound.rs", 1039 ], 1040 rust_types: &[ 1041 "radroots_event::listing::classified::ClassifiedListingPartition", 1042 "radroots_event::food::availability::FoodAvailabilityError", 1043 "radroots_event_codec::food_availability::inbound::RadrootsFoodAvailabilityImageDiagnostic", 1044 "radroots_event_codec::food_availability::inbound::RadrootsFoodAvailabilityProjectionError", 1045 "radroots_event_codec::food_availability::inbound::RadrootsFoodAvailabilityProjectionOutcome", 1046 "radroots_event_codec::food_availability::inbound::RadrootsInboundFoodAvailabilityImage", 1047 "radroots_event_codec::food_availability::inbound::RadrootsInboundFoodAvailabilityProjection", 1048 "radroots_event_codec::verify::RadrootsSignatureVerifiedEvent", 1049 ], 1050 case_kinds: &[ 1051 "food_availability.project_verified_event.valid", 1052 "food_availability.project_verified_event.invalid", 1053 ], 1054 }, 1055 FoodAvailabilityOperationExpectation { 1056 key: "food_availability_verify_and_admit_event", 1057 id: "food_availability.verify_and_admit_event", 1058 inputs: &["EventEnvelope"], 1059 outputs: &["RadrootsFoodAvailabilityAdmissionOutcome"], 1060 error_class: "admission_error", 1061 signing: "nip01", 1062 rust_modules: &[ 1063 "crates/event_codec/src/food_availability/admission.rs", 1064 "crates/event_codec/src/food_availability/inbound.rs", 1065 "crates/event_codec/src/verification.rs", 1066 ], 1067 rust_types: &[ 1068 "radroots_event::envelope::EventEnvelope", 1069 "radroots_event_codec::food_availability::admission::RadrootsAdmittedFoodAvailabilityEvent", 1070 "radroots_event_codec::food_availability::admission::RadrootsExcludedClassifiedListingCandidate", 1071 "radroots_event_codec::food_availability::admission::RadrootsFoodAvailabilityAdmissionError", 1072 "radroots_event_codec::food_availability::admission::RadrootsFoodAvailabilityAdmissionOutcome", 1073 "radroots_event_codec::food_availability::inbound::RadrootsInboundFoodAvailabilityProjection", 1074 "radroots_event_codec::verify::RadrootsSignatureVerifiedEvent", 1075 ], 1076 case_kinds: &[ 1077 "food_availability.verify_and_admit_event.valid", 1078 "food_availability.verify_and_admit_event.invalid", 1079 ], 1080 }, 1081 FoodAvailabilityOperationExpectation { 1082 key: "food_availability_validate_revision", 1083 id: "food_availability.validate_revision", 1084 inputs: &["RadrootsSignatureVerifiedEvent"], 1085 outputs: &["Unit"], 1086 error_class: "validation_error", 1087 signing: "none", 1088 rust_modules: &[ 1089 "crates/event_codec/src/food_availability/inbound.rs", 1090 "crates/event_codec/src/food_availability/revision.rs", 1091 ], 1092 rust_types: &[ 1093 "radroots_event_codec::food_availability::inbound::RadrootsFoodAvailabilityProjectionError", 1094 "radroots_event_codec::food_availability::revision::RadrootsFoodAvailabilityRevisionError", 1095 "radroots_event_codec::verify::RadrootsSignatureVerifiedEvent", 1096 ], 1097 case_kinds: &[ 1098 "food_availability.validate_revision.valid", 1099 "food_availability.validate_revision.invalid", 1100 ], 1101 }, 1102 ]; 1103 const FOOD_AVAILABILITY_CASE_KINDS: [&str; 8] = [ 1104 "food_availability.build_authored_draft.valid", 1105 "food_availability.build_authored_draft.invalid", 1106 "food_availability.project_verified_event.valid", 1107 "food_availability.project_verified_event.invalid", 1108 "food_availability.verify_and_admit_event.valid", 1109 "food_availability.verify_and_admit_event.invalid", 1110 "food_availability.validate_revision.valid", 1111 "food_availability.validate_revision.invalid", 1112 ]; 1113 const FOOD_AVAILABILITY_VECTOR_EXPECTATIONS: [(&str, &str); 40] = [ 1114 ( 1115 "food_authored_unit_g_001", 1116 "food_availability.build_authored_draft.valid", 1117 ), 1118 ( 1119 "food_authored_unit_kg_002", 1120 "food_availability.build_authored_draft.valid", 1121 ), 1122 ( 1123 "food_authored_unit_lb_003", 1124 "food_availability.build_authored_draft.valid", 1125 ), 1126 ( 1127 "food_authored_unit_oz_004", 1128 "food_availability.build_authored_draft.valid", 1129 ), 1130 ( 1131 "food_authored_unit_each_005", 1132 "food_availability.build_authored_draft.valid", 1133 ), 1134 ( 1135 "food_authored_unit_dozen_006", 1136 "food_availability.build_authored_draft.valid", 1137 ), 1138 ( 1139 "food_authored_unit_bunch_007", 1140 "food_availability.build_authored_draft.valid", 1141 ), 1142 ( 1143 "food_authored_unit_punnet_008", 1144 "food_availability.build_authored_draft.valid", 1145 ), 1146 ( 1147 "food_authored_unit_bag_009", 1148 "food_availability.build_authored_draft.valid", 1149 ), 1150 ( 1151 "food_authored_unit_basket_010", 1152 "food_availability.build_authored_draft.valid", 1153 ), 1154 ( 1155 "food_authored_wire_budget_ascii_max_011", 1156 "food_availability.build_authored_draft.valid", 1157 ), 1158 ( 1159 "food_authored_wire_budget_escaped_overflow_012", 1160 "food_availability.build_authored_draft.invalid", 1161 ), 1162 ( 1163 "food_authored_future_published_at_013", 1164 "food_availability.build_authored_draft.invalid", 1165 ), 1166 ( 1167 "food_admission_normalizes_decimal_currency_014", 1168 "food_availability.project_verified_event.valid", 1169 ), 1170 ( 1171 "food_admission_optional_standard_tags_015", 1172 "food_availability.verify_and_admit_event.valid", 1173 ), 1174 ( 1175 "food_admission_excludes_operational_before_validation_016", 1176 "food_availability.project_verified_event.valid", 1177 ), 1178 ( 1179 "food_admission_excludes_generic_nip99_017", 1180 "food_availability.project_verified_event.valid", 1181 ), 1182 ( 1183 "food_admission_rejects_ambiguous_markers_018", 1184 "food_availability.project_verified_event.invalid", 1185 ), 1186 ( 1187 "food_admission_rejects_wrong_kind_019", 1188 "food_availability.project_verified_event.invalid", 1189 ), 1190 ( 1191 "food_admission_rejects_core_tag_shape_020", 1192 "food_availability.project_verified_event.invalid", 1193 ), 1194 ( 1195 "food_admission_rejects_prohibited_delivery_021", 1196 "food_availability.project_verified_event.invalid", 1197 ), 1198 ( 1199 "food_admission_rejects_price_frequency_022", 1200 "food_availability.project_verified_event.invalid", 1201 ), 1202 ( 1203 "food_admission_requires_price_unit_023", 1204 "food_availability.project_verified_event.invalid", 1205 ), 1206 ( 1207 "food_admission_bounds_raw_decimal_digits_024", 1208 "food_availability.project_verified_event.invalid", 1209 ), 1210 ( 1211 "food_admission_rejects_malformed_price_unit_025", 1212 "food_availability.project_verified_event.invalid", 1213 ), 1214 ( 1215 "food_admission_rejects_quantity_unit_mismatch_026", 1216 "food_availability.project_verified_event.invalid", 1217 ), 1218 ( 1219 "food_admission_rejects_status_027", 1220 "food_availability.project_verified_event.invalid", 1221 ), 1222 ( 1223 "food_admission_rejects_future_published_at_028", 1224 "food_availability.project_verified_event.invalid", 1225 ), 1226 ( 1227 "food_admission_preserves_ordered_image_diagnostics_029", 1228 "food_availability.project_verified_event.valid", 1229 ), 1230 ( 1231 "food_admission_bounds_image_projection_030", 1232 "food_availability.project_verified_event.valid", 1233 ), 1234 ( 1235 "food_admission_rejects_invalid_signature_031", 1236 "food_availability.verify_and_admit_event.invalid", 1237 ), 1238 ( 1239 "food_revision_accepts_later_created_at_032", 1240 "food_availability.validate_revision.valid", 1241 ), 1242 ( 1243 "food_revision_rejects_invalid_previous_033", 1244 "food_availability.validate_revision.invalid", 1245 ), 1246 ( 1247 "food_revision_rejects_invalid_current_034", 1248 "food_availability.validate_revision.invalid", 1249 ), 1250 ( 1251 "food_revision_rejects_identifier_coordinate_change_035", 1252 "food_availability.validate_revision.invalid", 1253 ), 1254 ( 1255 "food_revision_rejects_author_coordinate_change_036", 1256 "food_availability.validate_revision.invalid", 1257 ), 1258 ( 1259 "food_revision_rejects_published_at_change_037", 1260 "food_availability.validate_revision.invalid", 1261 ), 1262 ( 1263 "food_revision_rejects_older_created_at_038", 1264 "food_availability.validate_revision.invalid", 1265 ), 1266 ( 1267 "food_revision_equal_time_a_current_039", 1268 "food_availability.validate_revision.invalid", 1269 ), 1270 ( 1271 "food_revision_equal_time_b_current_040", 1272 "food_availability.validate_revision.valid", 1273 ), 1274 ]; 1275 #[derive(Debug, Deserialize)] 1276 #[serde(deny_unknown_fields)] 1277 pub struct ContractManifest { 1278 pub contract: ManifestContract, 1279 pub surface: Surface, 1280 pub policy: Policy, 1281 } 1282 1283 #[derive(Debug, Deserialize)] 1284 #[serde(deny_unknown_fields)] 1285 pub struct ManifestContract { 1286 pub name: String, 1287 pub version: String, 1288 pub source: String, 1289 } 1290 1291 #[derive(Debug, Deserialize)] 1292 #[serde(deny_unknown_fields)] 1293 pub struct Surface { 1294 pub model_crates: Vec<String>, 1295 pub algorithm_crates: Vec<String>, 1296 pub rust_crate_tiers: Option<RustCrateTiers>, 1297 pub internal_replica_crates: Option<InternalReplicaCrates>, 1298 } 1299 1300 #[derive(Debug, Deserialize)] 1301 #[serde(deny_unknown_fields)] 1302 pub struct RustCrateTiers { 1303 pub advanced_substrate: Vec<String>, 1304 pub published_support: Vec<String>, 1305 pub deferred_publication: Vec<String>, 1306 } 1307 1308 #[derive(Debug, Deserialize)] 1309 #[serde(deny_unknown_fields)] 1310 pub struct InternalReplicaCrates { 1311 pub schema: String, 1312 pub storage: String, 1313 pub sync: String, 1314 } 1315 1316 #[derive(Debug, Deserialize)] 1317 #[serde(deny_unknown_fields)] 1318 pub struct Policy { 1319 pub exclude_internal_workspace_crates: bool, 1320 pub require_reproducible_exports: bool, 1321 pub require_conformance_vectors: bool, 1322 pub replica: Option<ReplicaPolicy>, 1323 } 1324 1325 #[derive(Debug, Deserialize)] 1326 #[serde(deny_unknown_fields)] 1327 pub struct ReplicaPolicy { 1328 pub forbid_legacy_alias_identifiers: bool, 1329 pub require_transport_agnostic_sync_contract: bool, 1330 pub require_deterministic_emit_ingest: bool, 1331 } 1332 1333 #[derive(Debug, Deserialize)] 1334 #[serde(deny_unknown_fields)] 1335 pub struct ReplicaContractManifest { 1336 pub schema_version: u32, 1337 pub contract: ReplicaContractMetadata, 1338 pub crate_family: ReplicaContractCrateFamily, 1339 pub policy: ReplicaContractPolicy, 1340 pub transfer: ReplicaTransferContract, 1341 } 1342 1343 #[derive(Debug, Deserialize)] 1344 #[serde(deny_unknown_fields)] 1345 pub struct ReplicaContractMetadata { 1346 pub name: String, 1347 pub version: String, 1348 pub purpose: String, 1349 } 1350 1351 #[derive(Debug, Deserialize)] 1352 #[serde(deny_unknown_fields)] 1353 pub struct ReplicaContractCrateFamily { 1354 pub schema: String, 1355 pub storage: String, 1356 pub sync: String, 1357 } 1358 1359 #[derive(Debug, Deserialize)] 1360 #[serde(deny_unknown_fields)] 1361 pub struct ReplicaContractPolicy { 1362 pub transport_agnostic_sync_core: bool, 1363 pub deterministic_emit_and_ingest: bool, 1364 pub forbid_legacy_alias_identifiers: bool, 1365 pub profile_event_emission: String, 1366 pub unknown_sync_request_fields: String, 1367 pub classified_listing_signature_verification: String, 1368 pub classified_listing_head_selection: String, 1369 pub classified_listing_operational_projection: String, 1370 pub classified_listing_excluded_or_rejected_head: String, 1371 pub classified_listing_head_only_ingest: String, 1372 pub legacy_bare_envelope_ingest: String, 1373 pub legacy_ingest_feature: String, 1374 pub phase_1_ingest_replacement: String, 1375 pub future_product_ingest_input: String, 1376 } 1377 1378 #[derive(Debug, Deserialize)] 1379 #[serde(deny_unknown_fields)] 1380 pub struct ReplicaTransferContract { 1381 pub version: u32, 1382 pub source: String, 1383 pub constant: String, 1384 } 1385 1386 #[derive(Debug, Deserialize)] 1387 #[serde(deny_unknown_fields)] 1388 pub struct OperationsContractManifest { 1389 pub contract: ManifestContract, 1390 pub public: PublicContract, 1391 pub shared_types: SharedTypesContract, 1392 pub errors: ErrorClassesContract, 1393 pub operations: BTreeMap<String, PublicOperationContract>, 1394 pub implementation_provenance: Option<ImplementationProvenance>, 1395 } 1396 1397 #[derive(Debug, Deserialize)] 1398 #[serde(deny_unknown_fields)] 1399 pub struct PublicContract { 1400 pub domains: Vec<String>, 1401 } 1402 1403 #[derive(Debug, Deserialize)] 1404 #[serde(deny_unknown_fields)] 1405 pub struct SharedTypesContract { 1406 pub public: Vec<String>, 1407 } 1408 1409 #[derive(Debug, Deserialize)] 1410 #[serde(deny_unknown_fields)] 1411 pub struct ErrorClassesContract { 1412 pub classes: Vec<String>, 1413 } 1414 1415 #[derive(Debug, Deserialize)] 1416 #[serde(deny_unknown_fields)] 1417 pub struct ImplementationProvenance { 1418 pub model_crates: Vec<String>, 1419 pub algorithm_crates: Vec<String>, 1420 } 1421 1422 #[derive(Debug, Deserialize)] 1423 #[serde(deny_unknown_fields)] 1424 pub struct PublicOperationContract { 1425 pub domain: String, 1426 pub id: String, 1427 pub stability: String, 1428 pub inputs: Vec<String>, 1429 pub outputs: Vec<String>, 1430 pub error_class: String, 1431 #[allow(dead_code)] 1432 pub deterministic: bool, 1433 pub signing: String, 1434 pub transport: String, 1435 pub implementation: PublicOperationImplementation, 1436 pub conformance: PublicOperationConformance, 1437 } 1438 1439 #[derive(Debug, Deserialize)] 1440 #[serde(deny_unknown_fields)] 1441 pub struct PublicOperationImplementation { 1442 pub rust_modules: Vec<String>, 1443 pub rust_types: Vec<String>, 1444 } 1445 1446 #[derive(Debug, Deserialize)] 1447 #[serde(deny_unknown_fields)] 1448 pub struct PublicOperationConformance { 1449 pub vector: String, 1450 #[serde(default)] 1451 pub case_kinds: Vec<String>, 1452 } 1453 1454 #[derive(Debug, Deserialize, Serialize)] 1455 #[serde(deny_unknown_fields)] 1456 struct DeletionConformanceRawEvent { 1457 id: String, 1458 pubkey: String, 1459 created_at: u64, 1460 kind: u32, 1461 tags: Vec<Vec<String>>, 1462 content: String, 1463 sig: String, 1464 } 1465 1466 #[derive(Clone, Copy)] 1467 struct CalendarOperationExpectation { 1468 key: &'static str, 1469 id: &'static str, 1470 inputs: &'static [&'static str], 1471 outputs: &'static [&'static str], 1472 error_class: &'static str, 1473 rust_modules: &'static [&'static str], 1474 rust_types: &'static [&'static str], 1475 vector: &'static str, 1476 } 1477 1478 #[derive(Clone, Copy)] 1479 struct PostOperationExpectation { 1480 key: &'static str, 1481 id: &'static str, 1482 inputs: &'static [&'static str], 1483 outputs: &'static [&'static str], 1484 error_class: &'static str, 1485 signing: &'static str, 1486 rust_modules: &'static [&'static str], 1487 rust_types: &'static [&'static str], 1488 case_kinds: &'static [&'static str], 1489 } 1490 1491 #[derive(Clone, Copy)] 1492 struct CommentOperationExpectation { 1493 key: &'static str, 1494 id: &'static str, 1495 inputs: &'static [&'static str], 1496 outputs: &'static [&'static str], 1497 error_class: &'static str, 1498 signing: &'static str, 1499 rust_modules: &'static [&'static str], 1500 rust_types: &'static [&'static str], 1501 case_kinds: &'static [&'static str], 1502 } 1503 1504 #[derive(Clone, Copy)] 1505 struct DeletionOperationExpectation { 1506 key: &'static str, 1507 id: &'static str, 1508 vector: &'static str, 1509 inputs: &'static [&'static str], 1510 outputs: &'static [&'static str], 1511 error_class: &'static str, 1512 signing: &'static str, 1513 rust_modules: &'static [&'static str], 1514 rust_types: &'static [&'static str], 1515 case_kinds: &'static [&'static str], 1516 } 1517 1518 #[derive(Clone, Copy)] 1519 struct FoodAvailabilityOperationExpectation { 1520 key: &'static str, 1521 id: &'static str, 1522 inputs: &'static [&'static str], 1523 outputs: &'static [&'static str], 1524 error_class: &'static str, 1525 signing: &'static str, 1526 rust_modules: &'static [&'static str], 1527 rust_types: &'static [&'static str], 1528 case_kinds: &'static [&'static str], 1529 } 1530 1531 #[derive(Debug, Deserialize)] 1532 #[serde(deny_unknown_fields)] 1533 pub struct VersionPolicy { 1534 pub contract: VersionContract, 1535 pub semver: SemverRules, 1536 pub release_integrity: ReleaseIntegrityRules, 1537 } 1538 1539 #[derive(Debug, Deserialize)] 1540 #[serde(deny_unknown_fields)] 1541 pub struct VersionContract { 1542 pub version: String, 1543 pub stability: String, 1544 } 1545 1546 #[derive(Debug, Deserialize)] 1547 #[serde(deny_unknown_fields)] 1548 pub struct SemverRules { 1549 pub major_on: Vec<String>, 1550 pub minor_on: Vec<String>, 1551 pub patch_on: Vec<String>, 1552 } 1553 1554 #[derive(Debug, Deserialize)] 1555 #[serde(deny_unknown_fields)] 1556 pub struct ReleaseIntegrityRules { 1557 pub requires_conformance_pass: bool, 1558 pub requires_contract_manifest_diff: bool, 1559 pub requires_release_notes: bool, 1560 } 1561 1562 #[derive(Debug, Deserialize)] 1563 #[serde(deny_unknown_fields)] 1564 struct ReleaseRecord { 1565 schema_version: u32, 1566 release: ReleaseRecordMetadata, 1567 artifacts: ReleaseRecordArtifacts, 1568 changes: Vec<ReleaseRecordChange>, 1569 } 1570 1571 #[derive(Debug, Deserialize)] 1572 #[serde(deny_unknown_fields)] 1573 struct ReleaseRecordMetadata { 1574 version: String, 1575 previous_version: String, 1576 contract_base_version: String, 1577 status: String, 1578 } 1579 1580 #[derive(Debug, Deserialize)] 1581 #[serde(deny_unknown_fields)] 1582 struct ReleaseRecordArtifacts { 1583 changelog: String, 1584 manifest: String, 1585 operations: String, 1586 replica: String, 1587 conformance: String, 1588 publish_policy: String, 1589 #[serde(default)] 1590 sqlite_runtime: Option<String>, 1591 } 1592 1593 #[derive(Debug, Deserialize)] 1594 #[serde(deny_unknown_fields)] 1595 struct SqliteRuntimeContract { 1596 schema_version: u32, 1597 package: SqliteRuntimePackage, 1598 activation: SqliteRuntimeActivation, 1599 access: SqliteRuntimeAccess, 1600 sealed_native_adapter: SqliteRuntimeSealedNativeAdapter, 1601 migration: SqliteRuntimeMigration, 1602 } 1603 1604 #[derive(Debug, Deserialize)] 1605 #[serde(deny_unknown_fields)] 1606 struct SqliteRuntimePackage { 1607 name: String, 1608 version: String, 1609 source: String, 1610 checksum: String, 1611 } 1612 1613 #[derive(Debug, Deserialize)] 1614 #[serde(deny_unknown_fields)] 1615 struct SqliteRuntimeActivation { 1616 route: Vec<String>, 1617 } 1618 1619 #[derive(Debug, Deserialize)] 1620 #[serde(deny_unknown_fields)] 1621 struct SqliteRuntimeAccess { 1622 high_level_library: String, 1623 native_linkage_owner: String, 1624 native_linkage_package: String, 1625 maximum_native_linkages: u32, 1626 forbidden_high_level_dependencies: Vec<String>, 1627 } 1628 1629 #[derive(Debug, Deserialize)] 1630 #[serde(deny_unknown_fields)] 1631 struct SqliteRuntimeSealedNativeAdapter { 1632 owner_package: String, 1633 relative_module: String, 1634 capability: String, 1635 status: String, 1636 } 1637 1638 #[derive(Debug, Deserialize)] 1639 #[serde(deny_unknown_fields)] 1640 struct SqliteRuntimeMigration { 1641 owner: String, 1642 status: String, 1643 temporary_direct_dependencies: Vec<String>, 1644 } 1645 1646 #[derive(Debug, Deserialize)] 1647 #[serde(deny_unknown_fields)] 1648 struct ReleaseRecordChange { 1649 id: String, 1650 classification: String, 1651 semver_impacts: Vec<String>, 1652 summary: String, 1653 } 1654 1655 #[derive(Debug)] 1656 pub struct ContractBundle { 1657 pub root: PathBuf, 1658 pub manifest: ContractManifest, 1659 pub version: VersionPolicy, 1660 pub replica: ReplicaContractManifest, 1661 pub operations_manifest: OperationsContractManifest, 1662 } 1663 1664 #[derive(Debug, Deserialize)] 1665 struct WorkspaceCargoManifest { 1666 workspace: WorkspaceSection, 1667 } 1668 1669 #[derive(Debug, Deserialize)] 1670 struct WorkspaceSection { 1671 members: Vec<String>, 1672 } 1673 1674 #[derive(Debug, Deserialize)] 1675 struct WorkspaceVersionCargoManifest { 1676 workspace: WorkspaceVersionSection, 1677 } 1678 1679 #[derive(Debug, Deserialize)] 1680 struct WorkspaceVersionSection { 1681 members: Vec<String>, 1682 package: WorkspacePackageVersion, 1683 dependencies: BTreeMap<String, WorkspaceDependencyVersion>, 1684 } 1685 1686 #[derive(Debug, Deserialize)] 1687 struct WorkspacePackageVersion { 1688 version: String, 1689 #[serde(default)] 1690 repository: String, 1691 } 1692 1693 #[derive(Debug, Deserialize)] 1694 struct WorkspaceDependencyVersion { 1695 path: Option<String>, 1696 version: Option<String>, 1697 } 1698 1699 #[derive(Debug, Deserialize)] 1700 struct VersionedPackageCargoManifest { 1701 package: VersionedPackageSection, 1702 } 1703 1704 #[derive(Debug, Deserialize)] 1705 struct VersionedPackageSection { 1706 name: String, 1707 version: PackageVersionSource, 1708 } 1709 1710 #[derive(Debug, Deserialize)] 1711 #[serde(untagged)] 1712 enum PackageVersionSource { 1713 Literal(String), 1714 Workspace { workspace: bool }, 1715 } 1716 1717 #[derive(Debug, Deserialize)] 1718 struct CargoLockManifest { 1719 package: Vec<CargoLockPackage>, 1720 } 1721 1722 #[derive(Debug, Deserialize)] 1723 struct CargoLockPackage { 1724 name: String, 1725 version: String, 1726 source: Option<String>, 1727 checksum: Option<String>, 1728 } 1729 1730 #[derive(Debug, Deserialize)] 1731 struct PackageCargoManifest { 1732 package: PackageSection, 1733 } 1734 1735 #[derive(Debug, Deserialize)] 1736 struct PackageSection { 1737 name: String, 1738 publish: Option<PackagePublish>, 1739 } 1740 1741 #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] 1742 #[serde(untagged)] 1743 enum PackagePublish { 1744 Bool(bool), 1745 Registries(Vec<String>), 1746 } 1747 1748 #[cfg_attr(not(test), allow(dead_code))] 1749 #[derive(Debug, Deserialize)] 1750 struct CoverageRequiredFile { 1751 required: CoverageRequiredSection, 1752 } 1753 1754 #[cfg_attr(not(test), allow(dead_code))] 1755 #[derive(Debug, Deserialize)] 1756 struct CoverageRequiredSection { 1757 crates: Vec<String>, 1758 } 1759 1760 #[derive(Debug, Clone, PartialEq, Eq)] 1761 struct EventBoundaryRow { 1762 domain: String, 1763 kind: String, 1764 radroots_type: String, 1765 rpc_methods: BTreeSet<String>, 1766 } 1767 1768 #[derive(Clone, Copy)] 1769 struct EventBoundarySourceWitness { 1770 relative_path: &'static str, 1771 required_fragments: &'static [&'static str], 1772 } 1773 1774 #[derive(Clone, Copy)] 1775 struct EventBoundaryExpectation { 1776 domain: &'static str, 1777 kind: &'static str, 1778 radroots_type: &'static str, 1779 rpc_methods: &'static [&'static str], 1780 witnesses: &'static [EventBoundarySourceWitness], 1781 } 1782 1783 const PROFILE_WITNESSES: [EventBoundarySourceWitness; 5] = [ 1784 EventBoundarySourceWitness { 1785 relative_path: "crates/event/src/profile.rs", 1786 required_fragments: &["pub struct AuthoredProfile"], 1787 }, 1788 EventBoundarySourceWitness { 1789 relative_path: "crates/event_codec/src/profile/inbound/registry_v7.rs", 1790 required_fragments: &["pub struct RadrootsInboundProfileMetadata"], 1791 }, 1792 EventBoundarySourceWitness { 1793 relative_path: "crates/event_codec/src/profile/admission.rs", 1794 required_fragments: &[ 1795 "pub struct RadrootsAdmittedProfileEvent", 1796 "pub fn verify_and_admit_profile_event", 1797 ], 1798 }, 1799 EventBoundarySourceWitness { 1800 relative_path: "crates/event_codec/src/verification/v1.rs", 1801 required_fragments: &[ 1802 "pub struct RadrootsSignatureVerifiedEvent", 1803 "pub fn verify_nip01_event", 1804 ], 1805 }, 1806 EventBoundarySourceWitness { 1807 relative_path: "crates/event/src/kinds.rs", 1808 required_fragments: &["pub const KIND_PROFILE: u32 = 0;"], 1809 }, 1810 ]; 1811 1812 const FOLLOW_WITNESSES: [EventBoundarySourceWitness; 2] = [ 1813 EventBoundarySourceWitness { 1814 relative_path: "crates/event/src/follow.rs", 1815 required_fragments: &["pub struct Follow"], 1816 }, 1817 EventBoundarySourceWitness { 1818 relative_path: "crates/event/src/kinds.rs", 1819 required_fragments: &["pub const KIND_FOLLOW: u32 = 3;"], 1820 }, 1821 ]; 1822 1823 const POST_WITNESSES: [EventBoundarySourceWitness; 5] = [ 1824 EventBoundarySourceWitness { 1825 relative_path: "crates/event/src/post.rs", 1826 required_fragments: &[ 1827 "pub struct Post", 1828 "pub struct AuthoredUpdate", 1829 "pub struct AuthoredPhotoUpdate", 1830 "pub struct AuthoredAsk", 1831 ], 1832 }, 1833 EventBoundarySourceWitness { 1834 relative_path: "crates/event_codec/src/post/authored.rs", 1835 required_fragments: &[ 1836 "pub fn authored_update_to_wire_parts", 1837 "pub fn authored_photo_update_to_wire_parts", 1838 "pub fn authored_ask_to_wire_parts", 1839 ], 1840 }, 1841 EventBoundarySourceWitness { 1842 relative_path: "crates/event_codec/src/post/inbound/registry_v7.rs", 1843 required_fragments: &[ 1844 "pub struct RadrootsInboundPostProjection", 1845 "pub fn project_verified_post_event", 1846 ], 1847 }, 1848 EventBoundarySourceWitness { 1849 relative_path: "crates/event_codec/src/post/admission.rs", 1850 required_fragments: &[ 1851 "pub struct RadrootsAdmittedRootPostEvent", 1852 "pub struct RadrootsThreadExcludedPostCandidate", 1853 "pub enum RadrootsPostAdmissionOutcome", 1854 "pub fn verify_and_admit_post_event", 1855 ], 1856 }, 1857 EventBoundarySourceWitness { 1858 relative_path: "crates/event/src/kinds.rs", 1859 required_fragments: &["pub const KIND_POST: u32 = 1;"], 1860 }, 1861 ]; 1862 1863 const REPLY_WITNESSES: [EventBoundarySourceWitness; 5] = [ 1864 EventBoundarySourceWitness { 1865 relative_path: "crates/event/src/relay_hint.rs", 1866 required_fragments: &["pub struct NostrRelayHint"], 1867 }, 1868 EventBoundarySourceWitness { 1869 relative_path: "crates/event/src/reply.rs", 1870 required_fragments: &[ 1871 "pub struct Nip10ReplyReference", 1872 "pub struct AuthoredNip10Reply", 1873 ], 1874 }, 1875 EventBoundarySourceWitness { 1876 relative_path: "crates/event_codec/src/reply/inbound/registry_v7.rs", 1877 required_fragments: &[ 1878 "pub struct RadrootsInboundNip10ReplyProjection", 1879 "pub fn project_verified_nip10_reply_event", 1880 ], 1881 }, 1882 EventBoundarySourceWitness { 1883 relative_path: "crates/event_codec/src/reply/admission.rs", 1884 required_fragments: &[ 1885 "pub struct RadrootsAdmittedNip10ReplyEvent", 1886 "pub fn verify_and_admit_nip10_reply_event", 1887 ], 1888 }, 1889 EventBoundarySourceWitness { 1890 relative_path: "crates/nostr/src/events/reply.rs", 1891 required_fragments: &[ 1892 "pub struct Nip10ReplyBuilder", 1893 "pub fn build_nip10_reply_event", 1894 ], 1895 }, 1896 ]; 1897 1898 const COMMENT_WITNESSES: [EventBoundarySourceWitness; 7] = [ 1899 EventBoundarySourceWitness { 1900 relative_path: "crates/event/src/relay_hint.rs", 1901 required_fragments: &["pub struct NostrRelayHint"], 1902 }, 1903 EventBoundarySourceWitness { 1904 relative_path: "crates/event/src/comment.rs", 1905 required_fragments: &[ 1906 "pub enum Nip22CommentRoot", 1907 "pub enum Nip22CommentPosition", 1908 "pub struct AuthoredNip22Comment", 1909 ], 1910 }, 1911 EventBoundarySourceWitness { 1912 relative_path: "crates/event_codec/src/comment/authored.rs", 1913 required_fragments: &["pub fn authored_nip22_comment_to_wire_parts"], 1914 }, 1915 EventBoundarySourceWitness { 1916 relative_path: "crates/event_codec/src/comment/inbound/registry_v7.rs", 1917 required_fragments: &[ 1918 "pub struct RadrootsInboundNip22CommentProjection", 1919 "pub fn project_verified_nip22_comment_event", 1920 ], 1921 }, 1922 EventBoundarySourceWitness { 1923 relative_path: "crates/event_codec/src/comment/admission.rs", 1924 required_fragments: &[ 1925 "pub struct RadrootsAdmittedNip22CommentEvent", 1926 "pub fn verify_and_admit_nip22_comment_event", 1927 ], 1928 }, 1929 EventBoundarySourceWitness { 1930 relative_path: "crates/nostr/src/events/comment.rs", 1931 required_fragments: &[ 1932 "pub struct Nip22CommentBuilder", 1933 "pub fn build_nip22_comment_event", 1934 ], 1935 }, 1936 EventBoundarySourceWitness { 1937 relative_path: "crates/event/src/kinds.rs", 1938 required_fragments: &["pub const KIND_COMMENT: u32 = 1111;"], 1939 }, 1940 ]; 1941 1942 const DELETION_WITNESSES: [EventBoundarySourceWitness; 7] = [ 1943 EventBoundarySourceWitness { 1944 relative_path: "crates/event/src/deletion.rs", 1945 required_fragments: &["pub struct AuthoredNip09DeletionRequest"], 1946 }, 1947 EventBoundarySourceWitness { 1948 relative_path: "crates/event_codec/src/deletion/authored.rs", 1949 required_fragments: &["pub fn authored_nip09_deletion_request_to_wire_parts"], 1950 }, 1951 EventBoundarySourceWitness { 1952 relative_path: "crates/event_codec/src/deletion/reconciliation_v1.rs", 1953 required_fragments: &[ 1954 "pub struct RadrootsInboundNip09DeletionProjection", 1955 "pub fn project_verified_nip09_deletion_request_event", 1956 ], 1957 }, 1958 EventBoundarySourceWitness { 1959 relative_path: "crates/event_codec/src/deletion/reconciliation_v1.rs", 1960 required_fragments: &[ 1961 "pub struct RadrootsAdmittedNip09DeletionRequestEvent", 1962 "pub fn verify_and_admit_nip09_deletion_request_event", 1963 ], 1964 }, 1965 EventBoundarySourceWitness { 1966 relative_path: "crates/event_codec/src/deletion/reconciliation_v1.rs", 1967 required_fragments: &[ 1968 "pub enum RadrootsNip09SuppressionOutcome", 1969 "pub enum RadrootsNip09SuppressionReason", 1970 "pub struct RadrootsNip09EventReferenceEvidence", 1971 "pub struct RadrootsNip09AddressReferenceEvidence", 1972 "pub struct RadrootsNip09SuppressionDecision", 1973 "pub fn evaluate_nip09_suppression", 1974 ], 1975 }, 1976 EventBoundarySourceWitness { 1977 relative_path: "crates/nostr/src/events/deletion.rs", 1978 required_fragments: &[ 1979 "pub struct Nip09DeletionRequestBuilder", 1980 "pub fn build_nip09_deletion_request_event", 1981 ], 1982 }, 1983 EventBoundarySourceWitness { 1984 relative_path: "crates/event/src/kinds.rs", 1985 required_fragments: &["pub const KIND_DELETION_REQUEST: u32 = 5;"], 1986 }, 1987 ]; 1988 1989 const REACTION_WITNESSES: [EventBoundarySourceWitness; 2] = [ 1990 EventBoundarySourceWitness { 1991 relative_path: "crates/event/src/reaction.rs", 1992 required_fragments: &["pub struct Reaction"], 1993 }, 1994 EventBoundarySourceWitness { 1995 relative_path: "crates/event/src/kinds.rs", 1996 required_fragments: &["pub const KIND_REACTION: u32 = 7;"], 1997 }, 1998 ]; 1999 2000 const REPOST_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2001 EventBoundarySourceWitness { 2002 relative_path: "crates/event/src/repost.rs", 2003 required_fragments: &["pub struct Repost"], 2004 }, 2005 EventBoundarySourceWitness { 2006 relative_path: "crates/event/src/kinds.rs", 2007 required_fragments: &["pub const KIND_REPOST: u32 = 6;"], 2008 }, 2009 ]; 2010 2011 const GENERIC_REPOST_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2012 EventBoundarySourceWitness { 2013 relative_path: "crates/event/src/repost.rs", 2014 required_fragments: &["pub struct GenericRepost"], 2015 }, 2016 EventBoundarySourceWitness { 2017 relative_path: "crates/event/src/kinds.rs", 2018 required_fragments: &["pub const KIND_GENERIC_REPOST: u32 = 16;"], 2019 }, 2020 ]; 2021 2022 const SEAL_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2023 EventBoundarySourceWitness { 2024 relative_path: "crates/event/src/seal.rs", 2025 required_fragments: &["pub struct Seal"], 2026 }, 2027 EventBoundarySourceWitness { 2028 relative_path: "crates/event/src/kinds.rs", 2029 required_fragments: &["pub const KIND_SEAL: u32 = 13;"], 2030 }, 2031 ]; 2032 2033 const MESSAGE_WITNESSES: [EventBoundarySourceWitness; 4] = [ 2034 EventBoundarySourceWitness { 2035 relative_path: "crates/event/src/message.rs", 2036 required_fragments: &["pub struct Message"], 2037 }, 2038 EventBoundarySourceWitness { 2039 relative_path: "crates/event/src/kinds.rs", 2040 required_fragments: &["pub const KIND_MESSAGE: u32 = 14;"], 2041 }, 2042 EventBoundarySourceWitness { 2043 relative_path: "crates/nostr/src/nip17.rs", 2044 required_fragments: &["pub async fn wrap_message<T>(", "KIND_MESSAGE =>"], 2045 }, 2046 EventBoundarySourceWitness { 2047 relative_path: "crates/nostr/src/lib.rs", 2048 required_fragments: &["pub mod nip17;"], 2049 }, 2050 ]; 2051 2052 const MESSAGE_FILE_WITNESSES: [EventBoundarySourceWitness; 4] = [ 2053 EventBoundarySourceWitness { 2054 relative_path: "crates/event/src/message_file.rs", 2055 required_fragments: &["pub struct MessageFile"], 2056 }, 2057 EventBoundarySourceWitness { 2058 relative_path: "crates/event/src/kinds.rs", 2059 required_fragments: &["pub const KIND_MESSAGE_FILE: u32 = 15;"], 2060 }, 2061 EventBoundarySourceWitness { 2062 relative_path: "crates/nostr/src/nip17.rs", 2063 required_fragments: &["pub async fn wrap_message_file<T>(", "KIND_MESSAGE_FILE =>"], 2064 }, 2065 EventBoundarySourceWitness { 2066 relative_path: "crates/nostr/src/lib.rs", 2067 required_fragments: &["pub mod nip17;"], 2068 }, 2069 ]; 2070 2071 const GIFT_WRAP_WITNESSES: [EventBoundarySourceWitness; 4] = [ 2072 EventBoundarySourceWitness { 2073 relative_path: "crates/event/src/gift_wrap.rs", 2074 required_fragments: &["pub struct GiftWrap"], 2075 }, 2076 EventBoundarySourceWitness { 2077 relative_path: "crates/event/src/kinds.rs", 2078 required_fragments: &["pub const KIND_GIFT_WRAP: u32 = 1059;"], 2079 }, 2080 EventBoundarySourceWitness { 2081 relative_path: "crates/nostr/src/nip17.rs", 2082 required_fragments: &["pub async fn unwrap_gift_wrap<T>("], 2083 }, 2084 EventBoundarySourceWitness { 2085 relative_path: "crates/nostr/src/lib.rs", 2086 required_fragments: &["pub mod nip17;"], 2087 }, 2088 ]; 2089 2090 const PUBLIC_FILE_METADATA_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2091 EventBoundarySourceWitness { 2092 relative_path: "crates/event/src/file_metadata.rs", 2093 required_fragments: &["pub struct FileMetadata"], 2094 }, 2095 EventBoundarySourceWitness { 2096 relative_path: "crates/event/src/kinds.rs", 2097 required_fragments: &["pub const KIND_PUBLIC_FILE_METADATA: u32 = KIND_FILE_METADATA;"], 2098 }, 2099 ]; 2100 2101 const REPORT_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2102 EventBoundarySourceWitness { 2103 relative_path: "crates/event/src/report.rs", 2104 required_fragments: &["pub struct Report"], 2105 }, 2106 EventBoundarySourceWitness { 2107 relative_path: "crates/event/src/kinds.rs", 2108 required_fragments: &["pub const KIND_REPORT: u32 = 1984;"], 2109 }, 2110 ]; 2111 2112 const LIST_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2113 EventBoundarySourceWitness { 2114 relative_path: "crates/event/src/list.rs", 2115 required_fragments: &["pub struct List"], 2116 }, 2117 EventBoundarySourceWitness { 2118 relative_path: "crates/event/src/kinds.rs", 2119 required_fragments: &[ 2120 "pub const KIND_LIST_MUTE: u32 = 10000;", 2121 "pub const KIND_LIST_GOOD_WIKI_RELAYS: u32 = 10102;", 2122 ], 2123 }, 2124 ]; 2125 2126 const RELAY_LIST_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2127 EventBoundarySourceWitness { 2128 relative_path: "crates/event/src/list.rs", 2129 required_fragments: &["pub struct List"], 2130 }, 2131 EventBoundarySourceWitness { 2132 relative_path: "crates/event/src/kinds.rs", 2133 required_fragments: &["pub const KIND_LIST_READ_WRITE_RELAYS: u32 = 10002;"], 2134 }, 2135 ]; 2136 2137 const LIST_SET_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2138 EventBoundarySourceWitness { 2139 relative_path: "crates/event/src/list_set.rs", 2140 required_fragments: &["pub struct ListSet"], 2141 }, 2142 EventBoundarySourceWitness { 2143 relative_path: "crates/event/src/kinds.rs", 2144 required_fragments: &[ 2145 "pub const KIND_LIST_SET_FOLLOW: u32 = 30000;", 2146 "pub const KIND_LIST_SET_MEDIA_STARTER_PACK: u32 = 39092;", 2147 ], 2148 }, 2149 ]; 2150 2151 const ARTICLE_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2152 EventBoundarySourceWitness { 2153 relative_path: "crates/event/src/article.rs", 2154 required_fragments: &["pub struct Article"], 2155 }, 2156 EventBoundarySourceWitness { 2157 relative_path: "crates/event/src/kinds.rs", 2158 required_fragments: &["pub const KIND_ARTICLE: u32 = 30023;"], 2159 }, 2160 ]; 2161 2162 const KNOWLEDGE_WITNESSES: [EventBoundarySourceWitness; 3] = [ 2163 EventBoundarySourceWitness { 2164 relative_path: "crates/event/src/knowledge.rs", 2165 required_fragments: &[ 2166 "pub struct WikiArticle", 2167 "pub struct KnowledgeClaim", 2168 "pub struct KnowledgeFieldReport", 2169 "pub struct EvidenceBounty", 2170 ], 2171 }, 2172 EventBoundarySourceWitness { 2173 relative_path: "crates/event/src/kinds.rs", 2174 required_fragments: &[ 2175 "pub const KIND_WIKI_MERGE_REQUEST: u32 = 818;", 2176 "pub const KIND_KNOWLEDGE_CLAIM: u32 = 3460;", 2177 "pub const KIND_KNOWLEDGE_SOURCE: u32 = 30450;", 2178 "pub const KIND_WIKI_ARTICLE: u32 = 30818;", 2179 ], 2180 }, 2181 EventBoundarySourceWitness { 2182 relative_path: "crates/event/src/contract/registry_v7.rs", 2183 required_fragments: &[ 2184 "Reducer::KnowledgeProjection", 2185 "\"radroots.wiki.article.v1\"", 2186 "\"radroots.knowledge.claim.v1\"", 2187 "pub fn validate_event_contract_shape", 2188 ], 2189 }, 2190 ]; 2191 2192 const APP_DATA_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2193 EventBoundarySourceWitness { 2194 relative_path: "crates/event/src/app_data.rs", 2195 required_fragments: &["pub struct AppData"], 2196 }, 2197 EventBoundarySourceWitness { 2198 relative_path: "crates/event/src/kinds.rs", 2199 required_fragments: &["pub const KIND_APP_DATA: u32 = 30078;"], 2200 }, 2201 ]; 2202 2203 const APP_HANDLER_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2204 EventBoundarySourceWitness { 2205 relative_path: "crates/event/src/kinds.rs", 2206 required_fragments: &["pub const KIND_APPLICATION_HANDLER: u32 = 31990;"], 2207 }, 2208 EventBoundarySourceWitness { 2209 relative_path: "crates/nostr/src/events/application_handler.rs", 2210 required_fragments: &["pub fn build_application_handler_event("], 2211 }, 2212 ]; 2213 2214 const CALENDAR_DATE_WITNESSES: [EventBoundarySourceWitness; 4] = [ 2215 EventBoundarySourceWitness { 2216 relative_path: "crates/event/src/calendar.rs", 2217 required_fragments: &[ 2218 "pub struct AuthoredCalendarDateEvent", 2219 "pub struct ParsedNip52CalendarDateEvent", 2220 "pub struct AdmittedCalendarDateEvent", 2221 ], 2222 }, 2223 EventBoundarySourceWitness { 2224 relative_path: "crates/event_codec/src/calendar/encode.rs", 2225 required_fragments: &["pub fn date_to_wire_parts("], 2226 }, 2227 EventBoundarySourceWitness { 2228 relative_path: "crates/event_codec/src/calendar/decode.rs", 2229 required_fragments: &[ 2230 "pub fn parse_nip52_calendar_date_event(", 2231 "pub fn admit_radroots_calendar_date_event(", 2232 ], 2233 }, 2234 EventBoundarySourceWitness { 2235 relative_path: "crates/event/src/kinds.rs", 2236 required_fragments: &["pub const KIND_CALENDAR_DATE_EVENT: u32 = 31922;"], 2237 }, 2238 ]; 2239 2240 const CALENDAR_TIME_WITNESSES: [EventBoundarySourceWitness; 4] = [ 2241 EventBoundarySourceWitness { 2242 relative_path: "crates/event/src/calendar.rs", 2243 required_fragments: &[ 2244 "pub struct AuthoredCalendarTimeEvent", 2245 "pub struct ParsedNip52CalendarTimeEvent", 2246 "pub struct AdmittedCalendarTimeEvent", 2247 ], 2248 }, 2249 EventBoundarySourceWitness { 2250 relative_path: "crates/event_codec/src/calendar/encode.rs", 2251 required_fragments: &["pub fn time_to_wire_parts("], 2252 }, 2253 EventBoundarySourceWitness { 2254 relative_path: "crates/event_codec/src/calendar/decode.rs", 2255 required_fragments: &[ 2256 "pub fn parse_nip52_calendar_time_event(", 2257 "pub fn admit_radroots_calendar_time_event(", 2258 ], 2259 }, 2260 EventBoundarySourceWitness { 2261 relative_path: "crates/event/src/kinds.rs", 2262 required_fragments: &["pub const KIND_CALENDAR_TIME_EVENT: u32 = 31923;"], 2263 }, 2264 ]; 2265 2266 const CALENDAR_WITNESSES: [EventBoundarySourceWitness; 4] = [ 2267 EventBoundarySourceWitness { 2268 relative_path: "crates/event/src/calendar.rs", 2269 required_fragments: &[ 2270 "pub struct AuthoredCalendar {", 2271 "pub struct ParsedNip52Calendar {", 2272 "pub struct AdmittedCalendar {", 2273 ], 2274 }, 2275 EventBoundarySourceWitness { 2276 relative_path: "crates/event_codec/src/calendar/encode.rs", 2277 required_fragments: &["pub fn calendar_to_wire_parts("], 2278 }, 2279 EventBoundarySourceWitness { 2280 relative_path: "crates/event_codec/src/calendar/decode.rs", 2281 required_fragments: &[ 2282 "pub fn parse_nip52_calendar(", 2283 "pub fn admit_radroots_calendar(", 2284 ], 2285 }, 2286 EventBoundarySourceWitness { 2287 relative_path: "crates/event/src/kinds.rs", 2288 required_fragments: &["pub const KIND_CALENDAR: u32 = KIND_LIST_SET_CALENDAR;"], 2289 }, 2290 ]; 2291 2292 const CALENDAR_RSVP_WITNESSES: [EventBoundarySourceWitness; 4] = [ 2293 EventBoundarySourceWitness { 2294 relative_path: "crates/event/src/calendar.rs", 2295 required_fragments: &[ 2296 "pub struct AuthoredCalendarEventRsvp {", 2297 "pub struct ParsedNip52CalendarEventRsvp {", 2298 "pub struct AdmittedCalendarEventRsvp {", 2299 ], 2300 }, 2301 EventBoundarySourceWitness { 2302 relative_path: "crates/event_codec/src/calendar/encode.rs", 2303 required_fragments: &["pub fn rsvp_to_wire_parts("], 2304 }, 2305 EventBoundarySourceWitness { 2306 relative_path: "crates/event_codec/src/calendar/decode.rs", 2307 required_fragments: &[ 2308 "pub fn parse_nip52_calendar_event_rsvp(", 2309 "pub fn admit_radroots_calendar_event_rsvp(", 2310 ], 2311 }, 2312 EventBoundarySourceWitness { 2313 relative_path: "crates/event/src/kinds.rs", 2314 required_fragments: &["pub const KIND_CALENDAR_EVENT_RSVP: u32 = 31925;"], 2315 }, 2316 ]; 2317 2318 const FARM_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2319 EventBoundarySourceWitness { 2320 relative_path: "crates/event/src/farm.rs", 2321 required_fragments: &["pub struct Farm"], 2322 }, 2323 EventBoundarySourceWitness { 2324 relative_path: "crates/event/src/kinds.rs", 2325 required_fragments: &["pub const KIND_FARM: u32 = 30340;"], 2326 }, 2327 ]; 2328 2329 const PLOT_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2330 EventBoundarySourceWitness { 2331 relative_path: "crates/event/src/plot.rs", 2332 required_fragments: &["pub struct Plot"], 2333 }, 2334 EventBoundarySourceWitness { 2335 relative_path: "crates/event/src/kinds.rs", 2336 required_fragments: &["pub const KIND_PLOT: u32 = 30350;"], 2337 }, 2338 ]; 2339 2340 const COOP_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2341 EventBoundarySourceWitness { 2342 relative_path: "crates/event/src/coop.rs", 2343 required_fragments: &["pub struct Coop"], 2344 }, 2345 EventBoundarySourceWitness { 2346 relative_path: "crates/event/src/kinds.rs", 2347 required_fragments: &["pub const KIND_COOP: u32 = 30360;"], 2348 }, 2349 ]; 2350 2351 const DOCUMENT_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2352 EventBoundarySourceWitness { 2353 relative_path: "crates/event/src/document.rs", 2354 required_fragments: &["pub struct Document"], 2355 }, 2356 EventBoundarySourceWitness { 2357 relative_path: "crates/event/src/kinds.rs", 2358 required_fragments: &["pub const KIND_DOCUMENT: u32 = 30361;"], 2359 }, 2360 ]; 2361 2362 const RESOURCE_AREA_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2363 EventBoundarySourceWitness { 2364 relative_path: "crates/event/src/resource_area.rs", 2365 required_fragments: &["pub struct ResourceArea"], 2366 }, 2367 EventBoundarySourceWitness { 2368 relative_path: "crates/event/src/kinds.rs", 2369 required_fragments: &["pub const KIND_RESOURCE_AREA: u32 = 30370;"], 2370 }, 2371 ]; 2372 2373 const RESOURCE_CAP_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2374 EventBoundarySourceWitness { 2375 relative_path: "crates/event/src/resource_cap.rs", 2376 required_fragments: &["pub struct ResourceHarvestCap"], 2377 }, 2378 EventBoundarySourceWitness { 2379 relative_path: "crates/event/src/kinds.rs", 2380 required_fragments: &["pub const KIND_RESOURCE_HARVEST_CAP: u32 = 30371;"], 2381 }, 2382 ]; 2383 2384 const OPERATIONAL_LISTING_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2385 EventBoundarySourceWitness { 2386 relative_path: "crates/event/src/operational_listing.rs", 2387 required_fragments: &["pub struct OperationalListing"], 2388 }, 2389 EventBoundarySourceWitness { 2390 relative_path: "crates/event/src/kinds.rs", 2391 required_fragments: &["pub const KIND_CLASSIFIED_LISTING: u32 = 30402;"], 2392 }, 2393 ]; 2394 2395 const FOOD_AVAILABILITY_WITNESSES: [EventBoundarySourceWitness; 6] = [ 2396 EventBoundarySourceWitness { 2397 relative_path: "crates/event/src/food_availability.rs", 2398 required_fragments: &[ 2399 "pub const RADROOTS_FOOD_AVAILABILITY_CONTRACT_ID: &str", 2400 "pub struct FoodAvailabilityDetails", 2401 ], 2402 }, 2403 EventBoundarySourceWitness { 2404 relative_path: "crates/event/src/kinds.rs", 2405 required_fragments: &["pub const KIND_CLASSIFIED_LISTING: u32 = 30402;"], 2406 }, 2407 EventBoundarySourceWitness { 2408 relative_path: "crates/event_codec/src/food_availability/authored.rs", 2409 required_fragments: &["pub fn authored_food_availability_to_wire_parts("], 2410 }, 2411 EventBoundarySourceWitness { 2412 relative_path: "crates/event_codec/src/food_availability/inbound/registry_v7.rs", 2413 required_fragments: &[ 2414 "pub struct RadrootsInboundFoodAvailabilityProjection", 2415 "pub fn project_verified_food_availability_event(", 2416 ], 2417 }, 2418 EventBoundarySourceWitness { 2419 relative_path: "crates/event_codec/src/food_availability/admission.rs", 2420 required_fragments: &[ 2421 "pub struct RadrootsAdmittedFoodAvailabilityEvent", 2422 "pub fn verify_and_admit_food_availability_event(", 2423 ], 2424 }, 2425 EventBoundarySourceWitness { 2426 relative_path: "crates/event_codec/src/food_availability/revision.rs", 2427 required_fragments: &["pub fn validate_food_availability_revision("], 2428 }, 2429 ]; 2430 2431 const DVM_REQUEST_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2432 EventBoundarySourceWitness { 2433 relative_path: "crates/event/src/job_request.rs", 2434 required_fragments: &["pub struct JobRequest"], 2435 }, 2436 EventBoundarySourceWitness { 2437 relative_path: "crates/event/src/kinds.rs", 2438 required_fragments: &[ 2439 "pub const KIND_JOB_REQUEST_MIN: u32 = 5000;", 2440 "pub const KIND_JOB_REQUEST_MAX: u32 = 5999;", 2441 ], 2442 }, 2443 ]; 2444 2445 const DVM_RESULT_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2446 EventBoundarySourceWitness { 2447 relative_path: "crates/event/src/job_result.rs", 2448 required_fragments: &["pub struct JobResult"], 2449 }, 2450 EventBoundarySourceWitness { 2451 relative_path: "crates/event/src/kinds.rs", 2452 required_fragments: &[ 2453 "pub const KIND_JOB_RESULT_MIN: u32 = 6000;", 2454 "pub const KIND_JOB_RESULT_MAX: u32 = 6999;", 2455 ], 2456 }, 2457 ]; 2458 2459 const DVM_FEEDBACK_WITNESSES: [EventBoundarySourceWitness; 2] = [ 2460 EventBoundarySourceWitness { 2461 relative_path: "crates/event/src/job_feedback.rs", 2462 required_fragments: &["pub struct JobFeedback"], 2463 }, 2464 EventBoundarySourceWitness { 2465 relative_path: "crates/event/src/kinds.rs", 2466 required_fragments: &["pub const KIND_JOB_FEEDBACK: u32 = 7000;"], 2467 }, 2468 ]; 2469 2470 const TRADE_PROPOSAL_WITNESSES: [EventBoundarySourceWitness; 3] = [ 2471 EventBoundarySourceWitness { 2472 relative_path: "crates/event/src/kinds.rs", 2473 required_fragments: &["pub const KIND_TRADE_PROPOSAL: u32 = 3470;"], 2474 }, 2475 EventBoundarySourceWitness { 2476 relative_path: "crates/event/src/trade.rs", 2477 required_fragments: &[ 2478 "pub const RADROOTS_TRADE_PROPOSAL_CONTRACT_ID: &str", 2479 "Self::Proposal => KIND_TRADE_PROPOSAL", 2480 ], 2481 }, 2482 EventBoundarySourceWitness { 2483 relative_path: "crates/event_codec/src/trade/mod.rs", 2484 required_fragments: &[ 2485 "pub fn trade_mutation_event_build", 2486 "pub fn trade_mutation_from_event", 2487 "pub fn trade_mutation_from_verified_event", 2488 "pub fn validate_trade_mutation_tags", 2489 ], 2490 }, 2491 ]; 2492 2493 const TRADE_DECISION_WITNESSES: [EventBoundarySourceWitness; 3] = [ 2494 EventBoundarySourceWitness { 2495 relative_path: "crates/event/src/kinds.rs", 2496 required_fragments: &["pub const KIND_TRADE_DECISION: u32 = 3471;"], 2497 }, 2498 EventBoundarySourceWitness { 2499 relative_path: "crates/event/src/trade.rs", 2500 required_fragments: &[ 2501 "pub const RADROOTS_TRADE_DECISION_CONTRACT_ID: &str", 2502 "Self::Decision => KIND_TRADE_DECISION", 2503 ], 2504 }, 2505 EventBoundarySourceWitness { 2506 relative_path: "crates/event_codec/src/trade/mod.rs", 2507 required_fragments: &[ 2508 "pub fn trade_mutation_event_build", 2509 "pub fn trade_mutation_from_event", 2510 "pub fn trade_mutation_from_verified_event", 2511 "pub fn validate_trade_mutation_tags", 2512 ], 2513 }, 2514 ]; 2515 2516 const TRADE_REVISION_PROPOSAL_WITNESSES: [EventBoundarySourceWitness; 3] = [ 2517 EventBoundarySourceWitness { 2518 relative_path: "crates/event/src/kinds.rs", 2519 required_fragments: &["pub const KIND_TRADE_REVISION_PROPOSAL: u32 = 3472;"], 2520 }, 2521 EventBoundarySourceWitness { 2522 relative_path: "crates/event/src/trade.rs", 2523 required_fragments: &[ 2524 "pub const RADROOTS_TRADE_REVISION_PROPOSAL_CONTRACT_ID: &str", 2525 "Self::RevisionProposal => KIND_TRADE_REVISION_PROPOSAL", 2526 ], 2527 }, 2528 EventBoundarySourceWitness { 2529 relative_path: "crates/event_codec/src/trade/mod.rs", 2530 required_fragments: &[ 2531 "pub fn trade_mutation_event_build", 2532 "pub fn trade_mutation_from_event", 2533 "pub fn trade_mutation_from_verified_event", 2534 "pub fn validate_trade_mutation_tags", 2535 ], 2536 }, 2537 ]; 2538 2539 const TRADE_REVISION_DECISION_WITNESSES: [EventBoundarySourceWitness; 3] = [ 2540 EventBoundarySourceWitness { 2541 relative_path: "crates/event/src/kinds.rs", 2542 required_fragments: &["pub const KIND_TRADE_REVISION_DECISION: u32 = 3473;"], 2543 }, 2544 EventBoundarySourceWitness { 2545 relative_path: "crates/event/src/trade.rs", 2546 required_fragments: &[ 2547 "pub const RADROOTS_TRADE_REVISION_DECISION_CONTRACT_ID: &str", 2548 "Self::RevisionDecision => KIND_TRADE_REVISION_DECISION", 2549 ], 2550 }, 2551 EventBoundarySourceWitness { 2552 relative_path: "crates/event_codec/src/trade/mod.rs", 2553 required_fragments: &[ 2554 "pub fn trade_mutation_event_build", 2555 "pub fn trade_mutation_from_event", 2556 "pub fn trade_mutation_from_verified_event", 2557 "pub fn validate_trade_mutation_tags", 2558 ], 2559 }, 2560 ]; 2561 2562 const TRADE_CANCELLATION_WITNESSES: [EventBoundarySourceWitness; 3] = [ 2563 EventBoundarySourceWitness { 2564 relative_path: "crates/event/src/kinds.rs", 2565 required_fragments: &["pub const KIND_TRADE_CANCELLATION: u32 = 3474;"], 2566 }, 2567 EventBoundarySourceWitness { 2568 relative_path: "crates/event/src/trade.rs", 2569 required_fragments: &[ 2570 "pub const RADROOTS_TRADE_CANCELLATION_CONTRACT_ID: &str", 2571 "Self::Cancellation => KIND_TRADE_CANCELLATION", 2572 ], 2573 }, 2574 EventBoundarySourceWitness { 2575 relative_path: "crates/event_codec/src/trade/mod.rs", 2576 required_fragments: &[ 2577 "pub fn trade_mutation_event_build", 2578 "pub fn trade_mutation_from_event", 2579 "pub fn trade_mutation_from_verified_event", 2580 "pub fn validate_trade_mutation_tags", 2581 ], 2582 }, 2583 ]; 2584 2585 const RELAY_DOC_WITNESSES: [EventBoundarySourceWitness; 1] = [EventBoundarySourceWitness { 2586 relative_path: "crates/event/src/relay_document.rs", 2587 required_fragments: &["pub struct RelayDocument"], 2588 }]; 2589 2590 const CANONICAL_EVENT_BOUNDARY_EXPECTATIONS: [EventBoundaryExpectation; 43] = [ 2591 EventBoundaryExpectation { 2592 domain: "profile", 2593 kind: "0", 2594 radroots_type: "AuthoredProfile / RadrootsInboundProfileMetadata", 2595 rpc_methods: &[ 2596 "events.profile.publish", 2597 "events.profile.list", 2598 "events.profile.get", 2599 ], 2600 witnesses: &PROFILE_WITNESSES, 2601 }, 2602 EventBoundaryExpectation { 2603 domain: "follow", 2604 kind: "3", 2605 radroots_type: "Follow", 2606 rpc_methods: &[ 2607 "events.follow.publish", 2608 "events.follow.list", 2609 "events.follow.get", 2610 ], 2611 witnesses: &FOLLOW_WITNESSES, 2612 }, 2613 EventBoundaryExpectation { 2614 domain: "post", 2615 kind: "1", 2616 radroots_type: "AuthoredUpdate / AuthoredPhotoUpdate / AuthoredAsk / RadrootsInboundPostProjection", 2617 rpc_methods: &["events.post.publish", "events.post.list", "events.post.get"], 2618 witnesses: &POST_WITNESSES, 2619 }, 2620 EventBoundaryExpectation { 2621 domain: "reply", 2622 kind: "1", 2623 radroots_type: "AuthoredNip10Reply / RadrootsInboundNip10ReplyProjection / RadrootsAdmittedNip10ReplyEvent / Nip10ReplyBuilder", 2624 rpc_methods: &[ 2625 "social.reply.build_authored_draft", 2626 "social.reply.project_verified_event", 2627 "social.reply.verify_and_admit_event", 2628 ], 2629 witnesses: &REPLY_WITNESSES, 2630 }, 2631 EventBoundaryExpectation { 2632 domain: "comment", 2633 kind: "1111", 2634 radroots_type: "AuthoredNip22Comment / RadrootsInboundNip22CommentProjection / RadrootsAdmittedNip22CommentEvent / Nip22CommentBuilder", 2635 rpc_methods: &[ 2636 "social.comment.build_authored_draft", 2637 "social.comment.project_verified_event", 2638 "social.comment.verify_and_admit_event", 2639 ], 2640 witnesses: &COMMENT_WITNESSES, 2641 }, 2642 EventBoundaryExpectation { 2643 domain: "deletion_request", 2644 kind: "5", 2645 radroots_type: "AuthoredNip09DeletionRequest / RadrootsInboundNip09DeletionProjection / RadrootsAdmittedNip09DeletionRequestEvent / RadrootsNip09SuppressionDecision / Nip09DeletionRequestBuilder", 2646 rpc_methods: &[ 2647 "social.deletion_request.build_authored_draft", 2648 "social.deletion_request.project_verified_event", 2649 "social.deletion_request.verify_and_admit_event", 2650 "social.deletion_request.evaluate_suppression", 2651 ], 2652 witnesses: &DELETION_WITNESSES, 2653 }, 2654 EventBoundaryExpectation { 2655 domain: "reaction", 2656 kind: "7", 2657 radroots_type: "Reaction", 2658 rpc_methods: &[ 2659 "events.reaction.publish", 2660 "events.reaction.list", 2661 "events.reaction.get", 2662 ], 2663 witnesses: &REACTION_WITNESSES, 2664 }, 2665 EventBoundaryExpectation { 2666 domain: "repost", 2667 kind: "6", 2668 radroots_type: "Repost", 2669 rpc_methods: &[ 2670 "events.repost.publish", 2671 "events.repost.list", 2672 "events.repost.get", 2673 ], 2674 witnesses: &REPOST_WITNESSES, 2675 }, 2676 EventBoundaryExpectation { 2677 domain: "generic_repost", 2678 kind: "16", 2679 radroots_type: "GenericRepost", 2680 rpc_methods: &[ 2681 "events.generic_repost.publish", 2682 "events.generic_repost.list", 2683 "events.generic_repost.get", 2684 ], 2685 witnesses: &GENERIC_REPOST_WITNESSES, 2686 }, 2687 EventBoundaryExpectation { 2688 domain: "seal", 2689 kind: "13", 2690 radroots_type: "Seal", 2691 rpc_methods: &["events.seal.encode", "events.seal.decode"], 2692 witnesses: &SEAL_WITNESSES, 2693 }, 2694 EventBoundaryExpectation { 2695 domain: "message", 2696 kind: "14", 2697 radroots_type: "Message", 2698 rpc_methods: &[ 2699 "events.message.publish", 2700 "events.message.list", 2701 "events.message.get", 2702 ], 2703 witnesses: &MESSAGE_WITNESSES, 2704 }, 2705 EventBoundaryExpectation { 2706 domain: "message_file", 2707 kind: "15", 2708 radroots_type: "MessageFile", 2709 rpc_methods: &[ 2710 "events.message_file.publish", 2711 "events.message_file.list", 2712 "events.message_file.get", 2713 ], 2714 witnesses: &MESSAGE_FILE_WITNESSES, 2715 }, 2716 EventBoundaryExpectation { 2717 domain: "gift_wrap", 2718 kind: "1059", 2719 radroots_type: "GiftWrap", 2720 rpc_methods: &[ 2721 "events.gift_wrap.publish", 2722 "events.gift_wrap.list", 2723 "events.gift_wrap.get", 2724 ], 2725 witnesses: &GIFT_WRAP_WITNESSES, 2726 }, 2727 EventBoundaryExpectation { 2728 domain: "public_file_metadata", 2729 kind: "1063", 2730 radroots_type: "FileMetadata", 2731 rpc_methods: &[ 2732 "events.public_file_metadata.publish", 2733 "events.public_file_metadata.list", 2734 "events.public_file_metadata.get", 2735 ], 2736 witnesses: &PUBLIC_FILE_METADATA_WITNESSES, 2737 }, 2738 EventBoundaryExpectation { 2739 domain: "report", 2740 kind: "1984", 2741 radroots_type: "Report", 2742 rpc_methods: &[ 2743 "events.report.publish", 2744 "events.report.list", 2745 "events.report.get", 2746 ], 2747 witnesses: &REPORT_WITNESSES, 2748 }, 2749 EventBoundaryExpectation { 2750 domain: "list", 2751 kind: "10000..10102", 2752 radroots_type: "List", 2753 rpc_methods: &["events.list.publish", "events.list.list", "events.list.get"], 2754 witnesses: &LIST_WITNESSES, 2755 }, 2756 EventBoundaryExpectation { 2757 domain: "relay_list", 2758 kind: "10002", 2759 radroots_type: "List", 2760 rpc_methods: &[ 2761 "events.relay_list.publish", 2762 "events.relay_list.list", 2763 "events.relay_list.get", 2764 ], 2765 witnesses: &RELAY_LIST_WITNESSES, 2766 }, 2767 EventBoundaryExpectation { 2768 domain: "list_set", 2769 kind: "30000..30007, 30015, 30030, 30063, 30267, 39089, 39092", 2770 radroots_type: "ListSet", 2771 rpc_methods: &[ 2772 "events.list_set.publish", 2773 "events.list_set.list", 2774 "events.list_set.get", 2775 ], 2776 witnesses: &LIST_SET_WITNESSES, 2777 }, 2778 EventBoundaryExpectation { 2779 domain: "article", 2780 kind: "30023", 2781 radroots_type: "Article", 2782 rpc_methods: &[ 2783 "events.article.publish", 2784 "events.article.list", 2785 "events.article.get", 2786 ], 2787 witnesses: &ARTICLE_WITNESSES, 2788 }, 2789 EventBoundaryExpectation { 2790 domain: "knowledge", 2791 kind: "818, 3460..3465, 30450..30451, 30818..30819", 2792 radroots_type: "RadrootsKnowledgeEvent", 2793 rpc_methods: &[ 2794 "events.knowledge.publish", 2795 "events.knowledge.list", 2796 "events.knowledge.get", 2797 ], 2798 witnesses: &KNOWLEDGE_WITNESSES, 2799 }, 2800 EventBoundaryExpectation { 2801 domain: "app_data", 2802 kind: "30078", 2803 radroots_type: "AppData", 2804 rpc_methods: &[ 2805 "events.app_data.publish", 2806 "events.app_data.list", 2807 "events.app_data.get", 2808 ], 2809 witnesses: &APP_DATA_WITNESSES, 2810 }, 2811 EventBoundaryExpectation { 2812 domain: "app_handler", 2813 kind: "31990", 2814 radroots_type: "KIND_APPLICATION_HANDLER", 2815 rpc_methods: &[ 2816 "events.app_handler.publish", 2817 "events.app_handler.list", 2818 "events.app_handler.get", 2819 ], 2820 witnesses: &APP_HANDLER_WITNESSES, 2821 }, 2822 EventBoundaryExpectation { 2823 domain: "calendar_date", 2824 kind: "31922", 2825 radroots_type: "AuthoredCalendarDateEvent / ParsedNip52CalendarDateEvent / AdmittedCalendarDateEvent", 2826 rpc_methods: &[ 2827 "events.calendar_date.publish", 2828 "events.calendar_date.list", 2829 "events.calendar_date.get", 2830 ], 2831 witnesses: &CALENDAR_DATE_WITNESSES, 2832 }, 2833 EventBoundaryExpectation { 2834 domain: "calendar_time", 2835 kind: "31923", 2836 radroots_type: "AuthoredCalendarTimeEvent / ParsedNip52CalendarTimeEvent / AdmittedCalendarTimeEvent", 2837 rpc_methods: &[ 2838 "events.calendar_time.publish", 2839 "events.calendar_time.list", 2840 "events.calendar_time.get", 2841 ], 2842 witnesses: &CALENDAR_TIME_WITNESSES, 2843 }, 2844 EventBoundaryExpectation { 2845 domain: "calendar", 2846 kind: "31924", 2847 radroots_type: "AuthoredCalendar / ParsedNip52Calendar / AdmittedCalendar", 2848 rpc_methods: &[ 2849 "events.calendar.publish", 2850 "events.calendar.list", 2851 "events.calendar.get", 2852 ], 2853 witnesses: &CALENDAR_WITNESSES, 2854 }, 2855 EventBoundaryExpectation { 2856 domain: "calendar_rsvp", 2857 kind: "31925", 2858 radroots_type: "AuthoredCalendarEventRsvp / ParsedNip52CalendarEventRsvp / AdmittedCalendarEventRsvp", 2859 rpc_methods: &[ 2860 "events.calendar_rsvp.publish", 2861 "events.calendar_rsvp.list", 2862 "events.calendar_rsvp.get", 2863 ], 2864 witnesses: &CALENDAR_RSVP_WITNESSES, 2865 }, 2866 EventBoundaryExpectation { 2867 domain: "farm", 2868 kind: "30340", 2869 radroots_type: "Farm", 2870 rpc_methods: &["events.farm.publish", "events.farm.list", "events.farm.get"], 2871 witnesses: &FARM_WITNESSES, 2872 }, 2873 EventBoundaryExpectation { 2874 domain: "plot", 2875 kind: "30350", 2876 radroots_type: "Plot", 2877 rpc_methods: &["events.plot.publish", "events.plot.list", "events.plot.get"], 2878 witnesses: &PLOT_WITNESSES, 2879 }, 2880 EventBoundaryExpectation { 2881 domain: "coop", 2882 kind: "30360", 2883 radroots_type: "Coop", 2884 rpc_methods: &["events.coop.publish", "events.coop.list", "events.coop.get"], 2885 witnesses: &COOP_WITNESSES, 2886 }, 2887 EventBoundaryExpectation { 2888 domain: "document", 2889 kind: "30361", 2890 radroots_type: "Document", 2891 rpc_methods: &[ 2892 "events.document.publish", 2893 "events.document.list", 2894 "events.document.get", 2895 ], 2896 witnesses: &DOCUMENT_WITNESSES, 2897 }, 2898 EventBoundaryExpectation { 2899 domain: "resource_area", 2900 kind: "30370", 2901 radroots_type: "ResourceArea", 2902 rpc_methods: &[ 2903 "events.resource_area.publish", 2904 "events.resource_area.list", 2905 "events.resource_area.get", 2906 ], 2907 witnesses: &RESOURCE_AREA_WITNESSES, 2908 }, 2909 EventBoundaryExpectation { 2910 domain: "resource_cap", 2911 kind: "30371", 2912 radroots_type: "ResourceHarvestCap", 2913 rpc_methods: &[ 2914 "events.resource_cap.publish", 2915 "events.resource_cap.list", 2916 "events.resource_cap.get", 2917 ], 2918 witnesses: &RESOURCE_CAP_WITNESSES, 2919 }, 2920 EventBoundaryExpectation { 2921 domain: "food_availability", 2922 kind: "30402", 2923 radroots_type: "FoodAvailabilityDetails / RadrootsInboundFoodAvailabilityProjection / RadrootsAdmittedFoodAvailabilityEvent / FoodAvailabilityBuilder", 2924 rpc_methods: &[ 2925 "food_availability.build_authored_draft", 2926 "food_availability.project_verified_event", 2927 "food_availability.verify_and_admit_event", 2928 "food_availability.validate_revision", 2929 ], 2930 witnesses: &FOOD_AVAILABILITY_WITNESSES, 2931 }, 2932 EventBoundaryExpectation { 2933 domain: "operational_listing", 2934 kind: "30402", 2935 radroots_type: "OperationalListing", 2936 rpc_methods: &[ 2937 "events.operational_listing.publish", 2938 "events.operational_listing.list", 2939 "events.operational_listing.get", 2940 ], 2941 witnesses: &OPERATIONAL_LISTING_WITNESSES, 2942 }, 2943 EventBoundaryExpectation { 2944 domain: "dvm_request", 2945 kind: "5000-5999", 2946 radroots_type: "JobRequest", 2947 rpc_methods: &[ 2948 "events.dvm_request.publish", 2949 "events.dvm_request.list", 2950 "events.dvm_request.get", 2951 ], 2952 witnesses: &DVM_REQUEST_WITNESSES, 2953 }, 2954 EventBoundaryExpectation { 2955 domain: "dvm_result", 2956 kind: "6000-6999", 2957 radroots_type: "JobResult", 2958 rpc_methods: &[ 2959 "events.dvm_result.publish", 2960 "events.dvm_result.list", 2961 "events.dvm_result.get", 2962 ], 2963 witnesses: &DVM_RESULT_WITNESSES, 2964 }, 2965 EventBoundaryExpectation { 2966 domain: "dvm_feedback", 2967 kind: "7000", 2968 radroots_type: "JobFeedback", 2969 rpc_methods: &[ 2970 "events.dvm_feedback.publish", 2971 "events.dvm_feedback.list", 2972 "events.dvm_feedback.get", 2973 ], 2974 witnesses: &DVM_FEEDBACK_WITNESSES, 2975 }, 2976 EventBoundaryExpectation { 2977 domain: "trade:proposal", 2978 kind: "3470", 2979 radroots_type: "TradeMutationEnvelopeV1", 2980 rpc_methods: &[ 2981 "trade.get_trade", 2982 "trade.list_trades", 2983 "trade.submit_proposal", 2984 ], 2985 witnesses: &TRADE_PROPOSAL_WITNESSES, 2986 }, 2987 EventBoundaryExpectation { 2988 domain: "trade:decision", 2989 kind: "3471", 2990 radroots_type: "TradeMutationEnvelopeV1", 2991 rpc_methods: &[ 2992 "trade.decide_candidate", 2993 "trade.get_trade", 2994 "trade.list_trades", 2995 ], 2996 witnesses: &TRADE_DECISION_WITNESSES, 2997 }, 2998 EventBoundaryExpectation { 2999 domain: "trade:revision_proposal", 3000 kind: "3472", 3001 radroots_type: "TradeMutationEnvelopeV1", 3002 rpc_methods: &[ 3003 "trade.get_trade", 3004 "trade.list_trades", 3005 "trade.propose_revision", 3006 ], 3007 witnesses: &TRADE_REVISION_PROPOSAL_WITNESSES, 3008 }, 3009 EventBoundaryExpectation { 3010 domain: "trade:revision_decision", 3011 kind: "3473", 3012 radroots_type: "TradeMutationEnvelopeV1", 3013 rpc_methods: &[ 3014 "trade.decide_candidate", 3015 "trade.get_trade", 3016 "trade.list_trades", 3017 ], 3018 witnesses: &TRADE_REVISION_DECISION_WITNESSES, 3019 }, 3020 EventBoundaryExpectation { 3021 domain: "trade:cancellation", 3022 kind: "3474", 3023 radroots_type: "TradeMutationEnvelopeV1", 3024 rpc_methods: &["trade.cancel_trade", "trade.get_trade", "trade.list_trades"], 3025 witnesses: &TRADE_CANCELLATION_WITNESSES, 3026 }, 3027 EventBoundaryExpectation { 3028 domain: "relay_doc", 3029 kind: "N/A", 3030 radroots_type: "RelayDocument", 3031 rpc_methods: &["system.relay_doc.get"], 3032 witnesses: &RELAY_DOC_WITNESSES, 3033 }, 3034 ]; 3035 3036 #[derive(Debug, Deserialize)] 3037 struct ReleaseContractFile { 3038 release: ReleaseSection, 3039 #[serde(default)] 3040 publication: Option<PublicationControl>, 3041 #[serde(default)] 3042 workspace_classification: Option<WorkspaceReleaseClassification>, 3043 #[serde(default)] 3044 classification: ReleaseClassification, 3045 #[serde(default)] 3046 publish: Option<ReleaseCrateSet>, 3047 #[serde(default)] 3048 internal: Option<ReleaseCrateSet>, 3049 publish_order: ReleaseCrateSet, 3050 } 3051 3052 #[derive(Debug, Default, Deserialize)] 3053 struct ReleaseClassification { 3054 #[serde(default)] 3055 public: Vec<String>, 3056 #[serde(default)] 3057 internal: Vec<String>, 3058 #[serde(default)] 3059 deferred: Vec<String>, 3060 #[serde(default)] 3061 retired: Vec<String>, 3062 #[serde(default)] 3063 yank_only: Vec<String>, 3064 } 3065 3066 #[derive(Debug, Deserialize)] 3067 struct ReleaseSection { 3068 version: String, 3069 } 3070 3071 #[derive(Debug, Deserialize)] 3072 struct PublicationControl { 3073 frozen: bool, 3074 registry: String, 3075 final_enablement_step: u16, 3076 #[serde(default)] 3077 spec_id: String, 3078 #[serde(default)] 3079 approved_packages: Vec<String>, 3080 #[serde(default)] 3081 local_packages: Vec<String>, 3082 #[serde(default)] 3083 external_packages: Vec<String>, 3084 } 3085 3086 #[derive(Debug, Deserialize)] 3087 struct WorkspaceReleaseClassification { 3088 #[serde(default)] 3089 private: Vec<String>, 3090 #[serde(default)] 3091 build_codegen: Vec<String>, 3092 #[serde(default)] 3093 test_support: Vec<String>, 3094 #[serde(default)] 3095 preview: Vec<String>, 3096 #[serde(default)] 3097 retired: Vec<String>, 3098 } 3099 3100 #[derive(Debug, Deserialize)] 3101 struct CratesReleaseArchitecture { 3102 spec_id: String, 3103 package_count: usize, 3104 repositories: CratesReleaseRepositories, 3105 package: Vec<CratesReleasePackage>, 3106 } 3107 3108 #[derive(Debug, Deserialize)] 3109 struct CratesReleaseRepositories { 3110 lib: CratesReleaseRepository, 3111 sdk: CratesReleaseRepository, 3112 } 3113 3114 #[derive(Debug, Deserialize)] 3115 struct CratesReleaseRepository { 3116 version: String, 3117 packages: Vec<String>, 3118 } 3119 3120 #[derive(Debug, Deserialize)] 3121 struct CratesReleasePackage { 3122 name: String, 3123 } 3124 3125 #[derive(Debug, Deserialize)] 3126 struct ConsolidationReleaseOwnership { 3127 canonical_rust_repository: String, 3128 } 3129 3130 #[derive(Debug, Deserialize)] 3131 struct ReleaseCrateSet { 3132 crates: Vec<String>, 3133 } 3134 3135 #[derive(Debug, Deserialize)] 3136 #[serde(deny_unknown_fields)] 3137 struct ConformanceVectorFile { 3138 suite: String, 3139 contract_version: String, 3140 vectors: Vec<ConformanceVectorEntry>, 3141 } 3142 3143 #[allow(dead_code)] 3144 #[derive(Debug, Deserialize)] 3145 #[serde(deny_unknown_fields)] 3146 struct ConformanceVectorEntry { 3147 id: String, 3148 kind: String, 3149 input: Value, 3150 expected: Option<Value>, 3151 expected_error_contains: Option<String>, 3152 } 3153 3154 impl ConformanceVectorEntry { 3155 fn expected_value(&self) -> Result<&Value, String> { 3156 self.expected.as_ref().ok_or_else(|| { 3157 format!( 3158 "conformance vector {} does not define an expected output", 3159 self.id 3160 ) 3161 }) 3162 } 3163 } 3164 3165 impl ReleaseContractFile { 3166 fn uses_classification(&self) -> bool { 3167 !self.classification.public.is_empty() 3168 || !self.classification.internal.is_empty() 3169 || !self.classification.deferred.is_empty() 3170 || !self.classification.retired.is_empty() 3171 || !self.classification.yank_only.is_empty() 3172 } 3173 3174 fn public_crates(&self) -> Vec<String> { 3175 if let Some(publication) = &self.publication 3176 && !publication.local_packages.is_empty() 3177 { 3178 return publication.local_packages.clone(); 3179 } 3180 if self.uses_classification() { 3181 return self.classification.public.clone(); 3182 } 3183 self.publish 3184 .as_ref() 3185 .map(|set| set.crates.clone()) 3186 .unwrap_or_default() 3187 } 3188 3189 fn internal_crates(&self) -> Vec<String> { 3190 if self.uses_classification() { 3191 return self.classification.internal.clone(); 3192 } 3193 self.internal 3194 .as_ref() 3195 .map(|set| set.crates.clone()) 3196 .unwrap_or_default() 3197 } 3198 3199 fn deferred_crates(&self) -> Vec<String> { 3200 self.classification.deferred.clone() 3201 } 3202 3203 fn retired_crates(&self) -> Vec<String> { 3204 self.classification.retired.clone() 3205 } 3206 3207 fn yank_only_crates(&self) -> Vec<String> { 3208 self.classification.yank_only.clone() 3209 } 3210 } 3211 3212 fn parse_toml<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<T, String> { 3213 let raw = match fs::read_to_string(path) { 3214 Ok(raw) => raw, 3215 Err(e) => return Err(format!("read {}: {e}", path.display())), 3216 }; 3217 match toml::from_str::<T>(&raw) { 3218 Ok(parsed) => Ok(parsed), 3219 Err(e) => Err(format!("parse {}: {e}", path.display())), 3220 } 3221 } 3222 3223 fn parse_json<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<T, String> { 3224 let raw = match fs::read_to_string(path) { 3225 Ok(raw) => raw, 3226 Err(e) => return Err(format!("read {}: {e}", path.display())), 3227 }; 3228 match serde_json::from_str::<T>(&raw) { 3229 Ok(parsed) => Ok(parsed), 3230 Err(e) => Err(format!("parse {}: {e}", path.display())), 3231 } 3232 } 3233 3234 fn resolve_event_boundary_matrix_path(workspace_root: &Path) -> Result<PathBuf, String> { 3235 let candidate = workspace_root.join(EVENT_BOUNDARY_MATRIX_RELATIVE); 3236 if candidate.is_file() { 3237 return Ok(candidate); 3238 } 3239 resolve_missing_event_boundary_matrix_path(workspace_root) 3240 } 3241 3242 fn missing_event_boundary_matrix_error() -> String { 3243 format!("canonical event matrix not found at {EVENT_BOUNDARY_MATRIX_RELATIVE}") 3244 } 3245 3246 #[cfg(not(test))] 3247 fn resolve_missing_event_boundary_matrix_path(_workspace_root: &Path) -> Result<PathBuf, String> { 3248 Err(missing_event_boundary_matrix_error()) 3249 } 3250 3251 #[cfg(test)] 3252 #[cfg_attr(coverage_nightly, coverage(off))] 3253 fn resolve_missing_event_boundary_matrix_path(workspace_root: &Path) -> Result<PathBuf, String> { 3254 if !should_synthesize_owner_contracts_for_tests(workspace_root) { 3255 return Err(missing_event_boundary_matrix_error()); 3256 } 3257 let path = std::env::temp_dir().join(format!( 3258 "radroots_xtask_event_boundary_{}.md", 3259 std::process::id() 3260 )); 3261 fs::write(&path, synthetic_event_boundary_matrix()) 3262 .map_err(|e| format!("write {}: {e}", path.display()))?; 3263 Ok(path) 3264 } 3265 3266 #[cfg(test)] 3267 #[cfg_attr(coverage_nightly, coverage(off))] 3268 fn synthetic_event_boundary_matrix() -> String { 3269 let mut raw = String::from( 3270 "# Event boundary matrix\n\n## Coverage matrix\n\n| Domain | Kind | Radroots Type | RPC Methods | Notes |\n| --- | --- | --- | --- | --- |\n", 3271 ); 3272 for expectation in CANONICAL_EVENT_BOUNDARY_EXPECTATIONS { 3273 raw.push_str(&format!( 3274 "| {} | {} | {} | {} | synthetic test matrix |\n", 3275 expectation.domain, 3276 expectation.kind, 3277 expectation.radroots_type, 3278 expectation.rpc_methods.join(", ") 3279 )); 3280 } 3281 raw.push('\n'); 3282 raw 3283 } 3284 3285 fn parse_event_boundary_matrix(path: &Path) -> Result<BTreeMap<String, EventBoundaryRow>, String> { 3286 let raw = match fs::read_to_string(path) { 3287 Ok(raw) => raw, 3288 Err(e) => return Err(format!("read {}: {e}", path.display())), 3289 }; 3290 let mut rows = BTreeMap::new(); 3291 let mut in_table = false; 3292 for line in raw.lines() { 3293 let trimmed = line.trim(); 3294 if trimmed == "| Domain | Kind | Radroots Type | RPC Methods | Notes |" { 3295 in_table = true; 3296 continue; 3297 } 3298 if !in_table { 3299 continue; 3300 } 3301 if trimmed.is_empty() { 3302 break; 3303 } 3304 if trimmed == "| --- | --- | --- | --- | --- |" { 3305 continue; 3306 } 3307 if !trimmed.starts_with('|') { 3308 break; 3309 } 3310 let columns = trimmed 3311 .trim_matches('|') 3312 .split('|') 3313 .map(|part| part.trim()) 3314 .collect::<Vec<_>>(); 3315 if columns.len() != 5 { 3316 return Err(format!( 3317 "canonical event matrix row in {} must have exactly 5 columns: {}", 3318 path.display(), 3319 trimmed 3320 )); 3321 } 3322 let domain = columns[0].to_string(); 3323 if domain.is_empty() { 3324 return Err(format!( 3325 "canonical event matrix row in {} must define a non-empty domain", 3326 path.display() 3327 )); 3328 } 3329 let rpc_methods = columns[3] 3330 .split(',') 3331 .map(str::trim) 3332 .filter(|item| !item.is_empty()) 3333 .map(|item| item.to_string()) 3334 .collect::<BTreeSet<_>>(); 3335 if rpc_methods.is_empty() { 3336 return Err(format!( 3337 "canonical event matrix row {} in {} must define rpc methods", 3338 domain, 3339 path.display() 3340 )); 3341 } 3342 let row = EventBoundaryRow { 3343 domain: domain.clone(), 3344 kind: columns[1].to_string(), 3345 radroots_type: columns[2].to_string(), 3346 rpc_methods, 3347 }; 3348 if rows.insert(domain.clone(), row).is_some() { 3349 return Err(format!( 3350 "canonical event matrix {} has duplicate domain row {}", 3351 path.display(), 3352 domain 3353 )); 3354 } 3355 } 3356 3357 if rows.is_empty() { 3358 return Err(format!( 3359 "canonical event matrix {} does not contain the coverage table", 3360 path.display() 3361 )); 3362 } 3363 3364 Ok(rows) 3365 } 3366 3367 fn validate_event_boundary_source_witness( 3368 workspace_root: &Path, 3369 domain: &str, 3370 witness: &EventBoundarySourceWitness, 3371 ) -> Result<(), String> { 3372 let path = workspace_root.join(witness.relative_path); 3373 let source = match fs::read_to_string(&path) { 3374 Ok(source) => source, 3375 Err(e) => return Err(format!("read {}: {e}", path.display())), 3376 }; 3377 for fragment in witness.required_fragments { 3378 if !source.contains(fragment) { 3379 return Err(format!( 3380 "canonical event row {} is missing required implementation fragment {} in {}", 3381 domain, 3382 fragment, 3383 path.display() 3384 )); 3385 } 3386 } 3387 Ok(()) 3388 } 3389 3390 fn validate_canonical_event_boundary_at_path( 3391 workspace_root: &Path, 3392 matrix_path: &Path, 3393 ) -> Result<(), String> { 3394 let rows = parse_event_boundary_matrix(matrix_path)?; 3395 let expected_domains = CANONICAL_EVENT_BOUNDARY_EXPECTATIONS 3396 .iter() 3397 .map(|row| row.domain.to_string()) 3398 .collect::<BTreeSet<_>>(); 3399 let actual_domains = rows.keys().cloned().collect::<BTreeSet<_>>(); 3400 if actual_domains != expected_domains { 3401 let missing = expected_domains 3402 .difference(&actual_domains) 3403 .cloned() 3404 .collect::<BTreeSet<_>>(); 3405 let extra = actual_domains 3406 .difference(&expected_domains) 3407 .cloned() 3408 .collect::<BTreeSet<_>>(); 3409 return Err(format!( 3410 "canonical event matrix {} is missing rows: {}; and includes unexpected rows: {}", 3411 matrix_path.display(), 3412 join_set(&missing), 3413 join_set(&extra) 3414 )); 3415 } 3416 3417 for expectation in CANONICAL_EVENT_BOUNDARY_EXPECTATIONS { 3418 let row = rows.get(expectation.domain).ok_or_else(|| { 3419 format!( 3420 "canonical event matrix {} is missing required row {}", 3421 matrix_path.display(), 3422 expectation.domain 3423 ) 3424 })?; 3425 if row.kind != expectation.kind { 3426 return Err(format!( 3427 "canonical event row {} kind drift: expected {}, got {}", 3428 expectation.domain, expectation.kind, row.kind 3429 )); 3430 } 3431 if row.radroots_type != expectation.radroots_type { 3432 return Err(format!( 3433 "canonical event row {} type drift: expected {}, got {}", 3434 expectation.domain, expectation.radroots_type, row.radroots_type 3435 )); 3436 } 3437 let expected_methods = expectation 3438 .rpc_methods 3439 .iter() 3440 .map(|method| (*method).to_string()) 3441 .collect::<BTreeSet<_>>(); 3442 if row.rpc_methods != expected_methods { 3443 return Err(format!( 3444 "canonical event row {} rpc drift: expected {}, got {}", 3445 expectation.domain, 3446 join_set(&expected_methods), 3447 join_set(&row.rpc_methods) 3448 )); 3449 } 3450 for witness in expectation.witnesses { 3451 validate_event_boundary_source_witness(workspace_root, expectation.domain, witness)?; 3452 } 3453 } 3454 3455 Ok(()) 3456 } 3457 3458 pub fn validate_canonical_event_boundary(workspace_root: &Path) -> Result<(), String> { 3459 let matrix_path = resolve_event_boundary_matrix_path(workspace_root)?; 3460 validate_canonical_event_boundary_at_path(workspace_root, &matrix_path) 3461 } 3462 3463 fn contract_root(workspace_root: &Path) -> PathBuf { 3464 workspace_root.join("contracts") 3465 } 3466 3467 fn conformance_root(workspace_root: &Path) -> PathBuf { 3468 workspace_root.join(CONFORMANCE_ROOT_RELATIVE) 3469 } 3470 3471 fn conformance_schema_path(workspace_root: &Path) -> PathBuf { 3472 workspace_root.join(CONFORMANCE_SCHEMA_RELATIVE) 3473 } 3474 3475 fn required_field_set(value: &Value, field: &str, path: &Path) -> Result<BTreeSet<String>, String> { 3476 let required = value 3477 .as_array() 3478 .ok_or_else(|| format!("{field} in {} must be an array", path.display()))?; 3479 let mut names = BTreeSet::new(); 3480 for item in required { 3481 let name = item 3482 .as_str() 3483 .ok_or_else(|| format!("{field} in {} must contain strings", path.display()))?; 3484 if name.trim().is_empty() { 3485 return Err(format!( 3486 "{field} in {} must not contain empty names", 3487 path.display() 3488 )); 3489 } 3490 names.insert(name.to_string()); 3491 } 3492 Ok(names) 3493 } 3494 3495 fn validate_string_schema_property( 3496 property: &Value, 3497 field: &str, 3498 path: &Path, 3499 min_length: Option<u64>, 3500 pattern: Option<&str>, 3501 ) -> Result<(), String> { 3502 let property = property 3503 .as_object() 3504 .ok_or_else(|| format!("{field} schema in {} must be an object", path.display()))?; 3505 let kind = property 3506 .get("type") 3507 .and_then(Value::as_str) 3508 .ok_or_else(|| format!("{field} schema in {} must declare type", path.display()))?; 3509 if kind != "string" { 3510 return Err(format!( 3511 "{field} schema in {} must use type=string", 3512 path.display() 3513 )); 3514 } 3515 if let Some(expected) = min_length { 3516 let actual = property 3517 .get("minLength") 3518 .and_then(Value::as_u64) 3519 .ok_or_else(|| format!("{field} schema in {} must set minLength", path.display()))?; 3520 if actual != expected { 3521 return Err(format!( 3522 "{field} schema in {} must set minLength={expected}", 3523 path.display() 3524 )); 3525 } 3526 } 3527 if let Some(expected) = pattern { 3528 let actual = property 3529 .get("pattern") 3530 .and_then(Value::as_str) 3531 .ok_or_else(|| format!("{field} schema in {} must set pattern", path.display()))?; 3532 if actual != expected { 3533 return Err(format!( 3534 "{field} schema in {} must set pattern {}", 3535 path.display(), 3536 expected 3537 )); 3538 } 3539 } 3540 Ok(()) 3541 } 3542 3543 fn validate_conformance_schema(workspace_root: &Path) -> Result<(), String> { 3544 let path = conformance_schema_path(workspace_root); 3545 let schema = parse_json::<Value>(&path)?; 3546 let schema_obj = schema.as_object().ok_or_else(|| { 3547 format!( 3548 "conformance schema {} must be a JSON object", 3549 path.display() 3550 ) 3551 })?; 3552 let schema_type = schema_obj 3553 .get("type") 3554 .and_then(Value::as_str) 3555 .ok_or_else(|| format!("conformance schema {} must declare type", path.display()))?; 3556 if schema_type != "object" { 3557 return Err(format!( 3558 "conformance schema {} must use type=object", 3559 path.display() 3560 )); 3561 } 3562 let additional = schema_obj 3563 .get("additionalProperties") 3564 .and_then(Value::as_bool) 3565 .ok_or_else(|| { 3566 format!( 3567 "conformance schema {} must declare additionalProperties", 3568 path.display() 3569 ) 3570 })?; 3571 if additional { 3572 return Err(format!( 3573 "conformance schema {} must disallow additionalProperties", 3574 path.display() 3575 )); 3576 } 3577 let root_required = required_field_set( 3578 schema_obj.get("required").ok_or_else(|| { 3579 format!( 3580 "conformance schema {} missing required list", 3581 path.display() 3582 ) 3583 })?, 3584 "required", 3585 &path, 3586 )?; 3587 let expected_root_required = BTreeSet::from([ 3588 "suite".to_string(), 3589 "contract_version".to_string(), 3590 "vectors".to_string(), 3591 ]); 3592 if root_required != expected_root_required { 3593 return Err(format!( 3594 "conformance schema {} must require suite, contract_version, and vectors", 3595 path.display() 3596 )); 3597 } 3598 let properties = schema_obj 3599 .get("properties") 3600 .and_then(Value::as_object) 3601 .ok_or_else(|| { 3602 format!( 3603 "conformance schema {} missing properties map", 3604 path.display() 3605 ) 3606 })?; 3607 validate_string_schema_property( 3608 properties.get("suite").ok_or_else(|| { 3609 format!( 3610 "conformance schema {} missing suite property", 3611 path.display() 3612 ) 3613 })?, 3614 "suite", 3615 &path, 3616 Some(1), 3617 None, 3618 )?; 3619 validate_string_schema_property( 3620 properties.get("contract_version").ok_or_else(|| { 3621 format!( 3622 "conformance schema {} missing contract_version property", 3623 path.display() 3624 ) 3625 })?, 3626 "contract_version", 3627 &path, 3628 None, 3629 Some("^[0-9]+\\.[0-9]+\\.[0-9]+$"), 3630 )?; 3631 let vectors = properties 3632 .get("vectors") 3633 .and_then(Value::as_object) 3634 .ok_or_else(|| { 3635 format!( 3636 "conformance schema {} missing vectors property", 3637 path.display() 3638 ) 3639 })?; 3640 let vectors_type = vectors 3641 .get("type") 3642 .and_then(Value::as_str) 3643 .ok_or_else(|| format!("vectors schema in {} must declare type", path.display()))?; 3644 if vectors_type != "array" { 3645 return Err(format!( 3646 "vectors schema in {} must use type=array", 3647 path.display() 3648 )); 3649 } 3650 let items = vectors 3651 .get("items") 3652 .and_then(Value::as_object) 3653 .ok_or_else(|| format!("vectors schema in {} must define items", path.display()))?; 3654 let items_type = items 3655 .get("type") 3656 .and_then(Value::as_str) 3657 .ok_or_else(|| format!("vector item schema in {} must declare type", path.display()))?; 3658 if items_type != "object" { 3659 return Err(format!( 3660 "vector item schema in {} must use type=object", 3661 path.display() 3662 )); 3663 } 3664 let items_additional = items 3665 .get("additionalProperties") 3666 .and_then(Value::as_bool) 3667 .ok_or_else(|| { 3668 format!( 3669 "vector item schema in {} must declare additionalProperties", 3670 path.display() 3671 ) 3672 })?; 3673 if items_additional { 3674 return Err(format!( 3675 "vector item schema in {} must disallow additionalProperties", 3676 path.display() 3677 )); 3678 } 3679 let item_required = required_field_set( 3680 items.get("required").ok_or_else(|| { 3681 format!( 3682 "vector item schema in {} missing required list", 3683 path.display() 3684 ) 3685 })?, 3686 "required", 3687 &path, 3688 )?; 3689 let expected_item_required = 3690 BTreeSet::from(["id".to_string(), "input".to_string(), "kind".to_string()]); 3691 if item_required != expected_item_required { 3692 return Err(format!( 3693 "vector item schema in {} must require id, kind, and input", 3694 path.display() 3695 )); 3696 } 3697 let expected_one_of = serde_json::json!([ 3698 { 3699 "required": ["expected"], 3700 "not": {"required": ["expected_error_contains"]} 3701 }, 3702 { 3703 "required": ["expected_error_contains"], 3704 "not": {"required": ["expected"]} 3705 } 3706 ]); 3707 if items.get("oneOf") != Some(&expected_one_of) { 3708 return Err(format!( 3709 "vector item schema in {} must require exactly one of expected or expected_error_contains", 3710 path.display() 3711 )); 3712 } 3713 let item_properties = items 3714 .get("properties") 3715 .and_then(Value::as_object) 3716 .ok_or_else(|| { 3717 format!( 3718 "vector item schema in {} missing properties", 3719 path.display() 3720 ) 3721 })?; 3722 validate_string_schema_property( 3723 item_properties.get("id").ok_or_else(|| { 3724 format!( 3725 "vector item schema in {} missing id property", 3726 path.display() 3727 ) 3728 })?, 3729 "id", 3730 &path, 3731 Some(1), 3732 None, 3733 )?; 3734 validate_string_schema_property( 3735 item_properties.get("kind").ok_or_else(|| { 3736 format!( 3737 "vector item schema in {} missing kind property", 3738 path.display() 3739 ) 3740 })?, 3741 "kind", 3742 &path, 3743 Some(1), 3744 None, 3745 )?; 3746 for field in ["input", "expected"] { 3747 let property = item_properties.get(field).ok_or_else(|| { 3748 format!( 3749 "vector item schema in {} missing {} property", 3750 path.display(), 3751 field 3752 ) 3753 })?; 3754 if !property.is_object() { 3755 return Err(format!( 3756 "vector item schema in {} must define {} as an object schema", 3757 path.display(), 3758 field 3759 )); 3760 } 3761 } 3762 validate_string_schema_property( 3763 item_properties 3764 .get("expected_error_contains") 3765 .ok_or_else(|| { 3766 format!( 3767 "vector item schema in {} missing expected_error_contains property", 3768 path.display() 3769 ) 3770 })?, 3771 "expected_error_contains", 3772 &path, 3773 Some(1), 3774 None, 3775 )?; 3776 Ok(()) 3777 } 3778 3779 fn base_contract_version(version: &str) -> &str { 3780 version.split_once('-').map_or(version, |(base, _)| base) 3781 } 3782 3783 fn parse_semver_version(version: &str) -> Result<Version, String> { 3784 Version::parse(version) 3785 .map_err(|error| format!("version {version} is not valid SemVer: {error}")) 3786 } 3787 3788 fn validate_contract_version_lockstep(bundle: &ContractBundle) -> Result<(), String> { 3789 let contract_version = bundle.manifest.contract.version.as_str(); 3790 parse_semver_version(contract_version)?; 3791 if bundle.version.contract.version != contract_version { 3792 return Err(format!( 3793 "version contract {} must match manifest contract version {}", 3794 bundle.version.contract.version, contract_version 3795 )); 3796 } 3797 if bundle.operations_manifest.contract.version != contract_version { 3798 return Err(format!( 3799 "operations contract version {} must match manifest contract version {}", 3800 bundle.operations_manifest.contract.version, contract_version 3801 )); 3802 } 3803 Ok(()) 3804 } 3805 3806 fn validate_workspace_version_lockstep( 3807 workspace_root: &Path, 3808 contract_version: &str, 3809 ) -> Result<(), String> { 3810 let workspace_manifest = 3811 parse_toml::<WorkspaceVersionCargoManifest>(&workspace_root.join("Cargo.toml"))?; 3812 let architecture_path = 3813 workspace_root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"); 3814 let governed_version = if architecture_path.is_file() { 3815 parse_toml::<CratesReleaseArchitecture>(&architecture_path)? 3816 .repositories 3817 .lib 3818 .version 3819 } else { 3820 contract_version.to_owned() 3821 }; 3822 if workspace_manifest.workspace.package.version != governed_version { 3823 return Err(format!( 3824 "workspace.package.version {} must match library repository version {}", 3825 workspace_manifest.workspace.package.version, governed_version 3826 )); 3827 } 3828 let mut governed_packages = BTreeMap::new(); 3829 for member in &workspace_manifest.workspace.members { 3830 let package_path = workspace_root.join(member).join("Cargo.toml"); 3831 let package = parse_toml::<VersionedPackageCargoManifest>(&package_path)?; 3832 let expected_version = governed_version.as_str(); 3833 match package.package.version { 3834 PackageVersionSource::Literal(ref version) if version == expected_version => {} 3835 PackageVersionSource::Literal(version) => { 3836 return Err(format!( 3837 "workspace member {member} package version {version} must match governed version {expected_version}" 3838 )); 3839 } 3840 PackageVersionSource::Workspace { workspace } => { 3841 return Err(format!( 3842 "workspace member {member} must set an explicit package version {expected_version}, not version.workspace = {workspace}, so mounted path consumers preserve the governed package version" 3843 )); 3844 } 3845 } 3846 governed_packages.insert( 3847 member.clone(), 3848 (package.package.name.clone(), expected_version.to_owned()), 3849 ); 3850 3851 if package.package.name.starts_with("radroots_") { 3852 let exact_requirement = format!("={expected_version}"); 3853 let dependency = workspace_manifest 3854 .workspace 3855 .dependencies 3856 .get(&package.package.name) 3857 .ok_or_else(|| { 3858 format!( 3859 "workspace dependency {} is required for member {member}", 3860 package.package.name 3861 ) 3862 })?; 3863 if dependency.path.as_deref() != Some(member.as_str()) { 3864 return Err(format!( 3865 "workspace dependency {} path must be {member}", 3866 package.package.name 3867 )); 3868 } 3869 if dependency.version.as_deref() != Some(exact_requirement.as_str()) { 3870 return Err(format!( 3871 "workspace dependency {} version must be the exact requirement {}", 3872 package.package.name, exact_requirement 3873 )); 3874 } 3875 } 3876 } 3877 3878 for (dependency_name, dependency) in &workspace_manifest.workspace.dependencies { 3879 let Some(path) = dependency.path.as_deref() else { 3880 continue; 3881 }; 3882 let Some((_, expected_version)) = governed_packages.get(path) else { 3883 continue; 3884 }; 3885 let exact_requirement = format!("={expected_version}"); 3886 if dependency.version.as_deref() != Some(exact_requirement.as_str()) { 3887 return Err(format!( 3888 "workspace path dependency {dependency_name} version must be the exact requirement {exact_requirement}" 3889 )); 3890 } 3891 } 3892 3893 validate_cargo_lock_version_lockstep(workspace_root, &governed_packages) 3894 } 3895 3896 fn validate_cargo_lock_version_lockstep( 3897 workspace_root: &Path, 3898 governed_packages: &BTreeMap<String, (String, String)>, 3899 ) -> Result<(), String> { 3900 let lock = parse_toml::<CargoLockManifest>(&workspace_root.join("Cargo.lock"))?; 3901 for (member, (package_name, expected_version)) in governed_packages { 3902 let workspace_entries = lock 3903 .package 3904 .iter() 3905 .filter(|package| package.name == *package_name && package.source.is_none()) 3906 .collect::<Vec<_>>(); 3907 if workspace_entries.len() != 1 { 3908 return Err(format!( 3909 "Cargo.lock must contain exactly one source-free entry for workspace member {member} ({package_name})" 3910 )); 3911 } 3912 if workspace_entries[0].version != *expected_version { 3913 return Err(format!( 3914 "Cargo.lock package {package_name} version {} must match governed version {expected_version}", 3915 workspace_entries[0].version 3916 )); 3917 } 3918 } 3919 Ok(()) 3920 } 3921 3922 fn valid_release_change_id(value: &str) -> bool { 3923 !value.is_empty() 3924 && !value.starts_with('-') 3925 && !value.ends_with('-') 3926 && !value.contains("--") 3927 && value 3928 .bytes() 3929 .all(|byte| byte.is_ascii_lowercase() || byte.is_ascii_digit() || byte == b'-') 3930 } 3931 3932 fn validate_release_record( 3933 workspace_root: &Path, 3934 contract_version: &str, 3935 semver: &SemverRules, 3936 ) -> Result<(), String> { 3937 let current_version = parse_semver_version(contract_version)?; 3938 let releases_root = workspace_root.join(RELEASES_ROOT_RELATIVE); 3939 if !releases_root.is_dir() { 3940 return Err(format!( 3941 "release records directory {RELEASES_ROOT_RELATIVE} is required" 3942 )); 3943 } 3944 let record_relative = format!("{RELEASES_ROOT_RELATIVE}/{contract_version}.toml"); 3945 let record = parse_toml::<ReleaseRecord>(&workspace_root.join(&record_relative))?; 3946 if record.schema_version != 1 { 3947 return Err(format!( 3948 "release record {record_relative} schema_version must be 1" 3949 )); 3950 } 3951 if record.release.version != contract_version { 3952 return Err(format!( 3953 "release record version {} must match contract version {contract_version}", 3954 record.release.version 3955 )); 3956 } 3957 let previous_version = parse_semver_version(&record.release.previous_version)?; 3958 if record.release.previous_version == contract_version { 3959 return Err("release.previous_version must differ from release.version".to_string()); 3960 } 3961 if current_version <= previous_version { 3962 return Err(format!( 3963 "release version {contract_version} must be greater than previous version {}", 3964 record.release.previous_version 3965 )); 3966 } 3967 let contract_base_version = format!( 3968 "{}.{}.{}", 3969 current_version.major, current_version.minor, current_version.patch 3970 ); 3971 if record.release.contract_base_version != contract_base_version { 3972 return Err(format!( 3973 "release.contract_base_version {} must match contract base version {}", 3974 record.release.contract_base_version, contract_base_version 3975 )); 3976 } 3977 if !matches!( 3978 record.release.status.as_str(), 3979 "unreleased" | "released" | "yanked" 3980 ) { 3981 return Err(format!( 3982 "release.status {} must be unreleased, released, or yanked", 3983 record.release.status 3984 )); 3985 } 3986 3987 let mut expected_artifacts = vec![ 3988 ( 3989 record.artifacts.changelog.as_str(), 3990 CHANGELOG_RELATIVE, 3991 false, 3992 ), 3993 ( 3994 record.artifacts.manifest.as_str(), 3995 "contracts/manifest.toml", 3996 false, 3997 ), 3998 ( 3999 record.artifacts.operations.as_str(), 4000 "contracts/operations.toml", 4001 false, 4002 ), 4003 ( 4004 record.artifacts.replica.as_str(), 4005 REPLICA_CONTRACT_RELATIVE, 4006 false, 4007 ), 4008 ( 4009 record.artifacts.conformance.as_str(), 4010 CONFORMANCE_ROOT_RELATIVE, 4011 true, 4012 ), 4013 ( 4014 record.artifacts.publish_policy.as_str(), 4015 RELEASE_POLICY_RELATIVE, 4016 false, 4017 ), 4018 ]; 4019 if let Some(sqlite_runtime) = record.artifacts.sqlite_runtime.as_deref() { 4020 expected_artifacts.push((sqlite_runtime, SQLITE_RUNTIME_CONTRACT_RELATIVE, false)); 4021 } 4022 for (actual, expected, directory) in expected_artifacts { 4023 if actual != expected { 4024 return Err(format!( 4025 "release artifact path {actual} must use canonical path {expected}" 4026 )); 4027 } 4028 let path = workspace_root.join(actual); 4029 if directory && !path.is_dir() || !directory && !path.is_file() { 4030 return Err(format!("release artifact {actual} does not exist")); 4031 } 4032 } 4033 4034 if record.changes.is_empty() { 4035 return Err("release record must contain at least one change".to_string()); 4036 } 4037 let mut change_ids = BTreeSet::new(); 4038 let mut has_breaking_change = false; 4039 let major_impacts = semver 4040 .major_on 4041 .iter() 4042 .map(String::as_str) 4043 .collect::<BTreeSet<_>>(); 4044 let minor_impacts = semver 4045 .minor_on 4046 .iter() 4047 .map(String::as_str) 4048 .collect::<BTreeSet<_>>(); 4049 let patch_impacts = semver 4050 .patch_on 4051 .iter() 4052 .map(String::as_str) 4053 .collect::<BTreeSet<_>>(); 4054 for change in &record.changes { 4055 if !valid_release_change_id(&change.id) { 4056 return Err(format!( 4057 "release change id {} must use lowercase kebab-case", 4058 change.id 4059 )); 4060 } 4061 if !change_ids.insert(change.id.as_str()) { 4062 return Err(format!( 4063 "release record has duplicate change id {}", 4064 change.id 4065 )); 4066 } 4067 if !matches!( 4068 change.classification.as_str(), 4069 "breaking" | "feature" | "fix" | "deprecation" | "security" | "docs" 4070 ) { 4071 return Err(format!( 4072 "release change {} has unsupported classification {}", 4073 change.id, change.classification 4074 )); 4075 } 4076 has_breaking_change |= change.classification == "breaking"; 4077 if change.semver_impacts.is_empty() { 4078 return Err(format!( 4079 "release change {} must declare at least one exact semver impact", 4080 change.id 4081 )); 4082 } 4083 let mut change_impacts = BTreeSet::new(); 4084 let mut has_major_impact = false; 4085 let mut has_minor_impact = false; 4086 let mut has_patch_impact = false; 4087 for impact in &change.semver_impacts { 4088 if !change_impacts.insert(impact.as_str()) { 4089 return Err(format!( 4090 "release change {} has duplicate semver impact {impact}", 4091 change.id 4092 )); 4093 } 4094 if major_impacts.contains(impact.as_str()) { 4095 has_major_impact = true; 4096 } else if minor_impacts.contains(impact.as_str()) { 4097 has_minor_impact = true; 4098 } else if patch_impacts.contains(impact.as_str()) { 4099 has_patch_impact = true; 4100 } else { 4101 return Err(format!( 4102 "release change {} semver impact {impact} is not governed by contracts/version.toml", 4103 change.id 4104 )); 4105 } 4106 } 4107 let classification_matches = if has_major_impact { 4108 change.classification == "breaking" 4109 } else if has_minor_impact { 4110 matches!(change.classification.as_str(), "feature" | "deprecation") 4111 } else if has_patch_impact { 4112 matches!(change.classification.as_str(), "fix" | "security" | "docs") 4113 } else { 4114 false 4115 }; 4116 if !classification_matches { 4117 return Err(format!( 4118 "release change {} classification {} does not match its governed semver impacts", 4119 change.id, change.classification 4120 )); 4121 } 4122 if change.summary.trim().is_empty() { 4123 return Err(format!( 4124 "release change {} summary must not be empty", 4125 change.id 4126 )); 4127 } 4128 } 4129 if current_version.major != previous_version.major && !has_breaking_change { 4130 return Err("a major version transition requires a breaking release change".to_string()); 4131 } 4132 4133 let declares_registry_sqlite = change_ids.contains("registry-sqlite-provenance"); 4134 if declares_registry_sqlite != record.artifacts.sqlite_runtime.is_some() { 4135 return Err(format!( 4136 "release change registry-sqlite-provenance and artifact {SQLITE_RUNTIME_CONTRACT_RELATIVE} must be declared together" 4137 )); 4138 } 4139 if declares_registry_sqlite { 4140 validate_sqlite_runtime_contract(workspace_root)?; 4141 } 4142 4143 validate_changelog_release_notes(workspace_root, contract_version) 4144 } 4145 4146 fn validate_sqlite_runtime_contract(workspace_root: &Path) -> Result<(), String> { 4147 const PACKAGE_NAME: &str = "libsqlite3-sys"; 4148 const PACKAGE_VERSION: &str = "0.37.0"; 4149 const PACKAGE_SOURCE: &str = "registry+https://github.com/rust-lang/crates.io-index"; 4150 const ACTIVATION_ROUTE: [&str; 3] = [ 4151 "radroots_event_store/sqlite", 4152 "sqlx/sqlite-bundled", 4153 "libsqlite3-sys/bundled", 4154 ]; 4155 const FORBIDDEN_HIGH_LEVEL_DEPENDENCIES: [&str; 6] = [ 4156 "diesel", "refinery", "rusqlite", "sea-orm", "sqlite", "sqlite3", 4157 ]; 4158 const TEMPORARY_DIRECT_DEPENDENCIES: [&str; 0] = []; 4159 4160 let contract = parse_toml::<SqliteRuntimeContract>( 4161 &workspace_root.join(SQLITE_RUNTIME_CONTRACT_RELATIVE), 4162 )?; 4163 if contract.schema_version != 2 4164 || contract.package.name != PACKAGE_NAME 4165 || contract.package.version != PACKAGE_VERSION 4166 || contract.package.source != PACKAGE_SOURCE 4167 || contract.activation.route != ACTIVATION_ROUTE 4168 || contract.access.high_level_library != "sqlx" 4169 || contract.access.native_linkage_owner != "sqlx" 4170 || contract.access.native_linkage_package != PACKAGE_NAME 4171 || contract.access.maximum_native_linkages != 1 4172 || contract.access.forbidden_high_level_dependencies != FORBIDDEN_HIGH_LEVEL_DEPENDENCIES 4173 || contract.sealed_native_adapter.owner_package != "radroots_service_sqlite" 4174 || contract.sealed_native_adapter.relative_module 4175 != "crates/service_sqlite/src/sqlite_native_backup.rs" 4176 || contract.sealed_native_adapter.capability != "incremental_online_backup" 4177 || contract.sealed_native_adapter.status != "active" 4178 || contract.migration.owner != "rcld-rshr-045" 4179 || contract.migration.status != "in_progress" 4180 || contract.migration.temporary_direct_dependencies != TEMPORARY_DIRECT_DEPENDENCIES 4181 { 4182 return Err(format!( 4183 "{SQLITE_RUNTIME_CONTRACT_RELATIVE} must govern the exact SQLx access, native SQLite linkage, sealed-adapter, and RCLD-045 migration policy" 4184 )); 4185 } 4186 if contract.package.checksum.len() != 64 4187 || !contract 4188 .package 4189 .checksum 4190 .bytes() 4191 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) 4192 { 4193 return Err(format!( 4194 "{SQLITE_RUNTIME_CONTRACT_RELATIVE} package.checksum must be a lowercase SHA-256 digest" 4195 )); 4196 } 4197 4198 let lock = parse_toml::<CargoLockManifest>(&workspace_root.join("Cargo.lock"))?; 4199 let matches = lock 4200 .package 4201 .iter() 4202 .filter(|package| { 4203 package.name == contract.package.name 4204 && package.version == contract.package.version 4205 && package.source.as_deref() == Some(contract.package.source.as_str()) 4206 && package.checksum.as_deref() == Some(contract.package.checksum.as_str()) 4207 }) 4208 .count(); 4209 if matches != 1 { 4210 return Err(format!( 4211 "Cargo.lock must contain exactly one package matching {SQLITE_RUNTIME_CONTRACT_RELATIVE}" 4212 )); 4213 } 4214 4215 let native_linkages = lock 4216 .package 4217 .iter() 4218 .filter(|package| package.name == contract.access.native_linkage_package) 4219 .count(); 4220 if native_linkages != contract.access.maximum_native_linkages as usize { 4221 return Err(format!( 4222 "Cargo.lock must contain exactly {} {} native linkage", 4223 contract.access.maximum_native_linkages, contract.access.native_linkage_package 4224 )); 4225 } 4226 4227 let workspace = parse_toml::<WorkspaceCargoManifest>(&workspace_root.join("Cargo.toml"))?; 4228 for member in &workspace.workspace.members { 4229 let package = 4230 parse_toml::<PackageCargoManifest>(&workspace_root.join(member).join("Cargo.toml"))?; 4231 if package.package.name == PACKAGE_NAME { 4232 return Err(format!( 4233 "workspace member {member} must not vendor registry package {PACKAGE_NAME}" 4234 )); 4235 } 4236 } 4237 4238 let actual_temporary = sqlite_forbidden_direct_dependencies( 4239 workspace_root, 4240 &workspace.workspace.members, 4241 &contract.access.forbidden_high_level_dependencies, 4242 )?; 4243 let expected_temporary = contract 4244 .migration 4245 .temporary_direct_dependencies 4246 .iter() 4247 .cloned() 4248 .collect::<BTreeSet<_>>(); 4249 if actual_temporary != expected_temporary { 4250 return Err(format!( 4251 "{SQLITE_RUNTIME_CONTRACT_RELATIVE} temporary direct dependency inventory is stale: expected {expected_temporary:?}, found {actual_temporary:?}" 4252 )); 4253 } 4254 4255 let direct_native_dependencies = sqlite_direct_dependency_owners( 4256 workspace_root, 4257 &workspace.workspace.members, 4258 PACKAGE_NAME, 4259 )?; 4260 let expected_direct_native_dependencies = BTreeSet::from([ 4261 "radroots_service_sqlite:libsqlite3-sys".to_owned(), 4262 "workspace:libsqlite3-sys".to_owned(), 4263 ]); 4264 if direct_native_dependencies != expected_direct_native_dependencies { 4265 return Err(format!( 4266 "direct {PACKAGE_NAME} access must be limited to the sealed adapter owner: expected {expected_direct_native_dependencies:?}, found {direct_native_dependencies:?}" 4267 )); 4268 } 4269 validate_sqlite_native_adapter_source(workspace_root, &contract.sealed_native_adapter)?; 4270 4271 let storage_sqlite = 4272 fs::read_to_string(workspace_root.join("crates/storage_sqlite/Cargo.toml")) 4273 .map_err(|error| format!("read crates/storage_sqlite/Cargo.toml: {error}"))?; 4274 let storage_sqlite: toml::Value = toml::from_str(&storage_sqlite) 4275 .map_err(|error| format!("parse crates/storage_sqlite/Cargo.toml: {error}"))?; 4276 let sqlite_features = storage_sqlite 4277 .get("dependencies") 4278 .and_then(|dependencies| dependencies.get("sqlx")) 4279 .and_then(|sqlx| sqlx.get("features")) 4280 .and_then(toml::Value::as_array) 4281 .ok_or_else(|| { 4282 "crates/storage_sqlite/Cargo.toml dependencies.sqlx.features is required".to_string() 4283 })?; 4284 if !sqlite_features 4285 .iter() 4286 .any(|feature| feature.as_str() == Some("sqlite-bundled")) 4287 { 4288 return Err( 4289 "crates/storage_sqlite/Cargo.toml dependencies.sqlx.features must activate sqlite-bundled" 4290 .to_string(), 4291 ); 4292 } 4293 Ok(()) 4294 } 4295 4296 fn validate_sqlite_native_adapter_source( 4297 workspace_root: &Path, 4298 adapter: &SqliteRuntimeSealedNativeAdapter, 4299 ) -> Result<(), String> { 4300 let adapter_path = workspace_root.join(&adapter.relative_module); 4301 let adapter_source = fs::read_to_string(&adapter_path) 4302 .map_err(|error| format!("read {}: {error}", adapter_path.display()))?; 4303 for required in [ 4304 "use libsqlite3_sys as ffi;", 4305 "LockedSqliteHandle", 4306 "ffi::sqlite3_backup_init", 4307 "ffi::sqlite3_backup_step", 4308 "ffi::sqlite3_backup_finish", 4309 ] { 4310 if !adapter_source.contains(required) { 4311 return Err(format!( 4312 "{} must contain the sealed native adapter boundary `{required}`", 4313 adapter_path.display() 4314 )); 4315 } 4316 } 4317 4318 let source_root = workspace_root.join("crates/service_sqlite/src"); 4319 for entry in WalkDir::new(&source_root).follow_links(false) { 4320 let entry = entry.map_err(|error| format!("walk {}: {error}", source_root.display()))?; 4321 if !entry.file_type().is_file() 4322 || entry 4323 .path() 4324 .extension() 4325 .and_then(|extension| extension.to_str()) 4326 != Some("rs") 4327 || entry.path() == adapter_path 4328 { 4329 continue; 4330 } 4331 let source = fs::read_to_string(entry.path()) 4332 .map_err(|error| format!("read {}: {error}", entry.path().display()))?; 4333 for forbidden in [ 4334 "libsqlite3_sys", 4335 "sqlite3_backup_", 4336 "unsafe {", 4337 "unsafe fn ", 4338 "unsafe impl ", 4339 ] { 4340 if source.contains(forbidden) { 4341 return Err(format!( 4342 "{} contains native SQLite or unsafe authority outside the sealed adapter: `{forbidden}`", 4343 entry.path().display() 4344 )); 4345 } 4346 } 4347 } 4348 Ok(()) 4349 } 4350 4351 fn sqlite_forbidden_direct_dependencies( 4352 workspace_root: &Path, 4353 members: &[String], 4354 forbidden: &[String], 4355 ) -> Result<BTreeSet<String>, String> { 4356 let forbidden = forbidden 4357 .iter() 4358 .map(String::as_str) 4359 .collect::<BTreeSet<_>>(); 4360 sqlite_direct_dependencies_matching(workspace_root, members, |dependency| { 4361 forbidden.contains(dependency) 4362 }) 4363 } 4364 4365 fn sqlite_direct_dependency_owners( 4366 workspace_root: &Path, 4367 members: &[String], 4368 dependency: &str, 4369 ) -> Result<BTreeSet<String>, String> { 4370 sqlite_direct_dependencies_matching(workspace_root, members, |candidate| { 4371 candidate == dependency 4372 }) 4373 } 4374 4375 fn sqlite_direct_dependencies_matching( 4376 workspace_root: &Path, 4377 members: &[String], 4378 matches: impl Fn(&str) -> bool, 4379 ) -> Result<BTreeSet<String>, String> { 4380 let mut found = BTreeSet::new(); 4381 let workspace_manifest = parse_toml::<toml::Value>(&workspace_root.join("Cargo.toml"))?; 4382 if let Some(dependencies) = workspace_manifest 4383 .get("workspace") 4384 .and_then(|workspace| workspace.get("dependencies")) 4385 .and_then(toml::Value::as_table) 4386 { 4387 collect_matching_dependencies("workspace", dependencies, &matches, &mut found); 4388 } 4389 4390 for member in members { 4391 let path = workspace_root.join(member).join("Cargo.toml"); 4392 let manifest = parse_toml::<toml::Value>(&path)?; 4393 let owner = manifest 4394 .get("package") 4395 .and_then(|package| package.get("name")) 4396 .and_then(toml::Value::as_str) 4397 .ok_or_else(|| format!("{} package.name is required", path.display()))?; 4398 for section in ["dependencies", "dev-dependencies", "build-dependencies"] { 4399 if let Some(dependencies) = manifest.get(section).and_then(toml::Value::as_table) { 4400 collect_matching_dependencies(owner, dependencies, &matches, &mut found); 4401 } 4402 } 4403 if let Some(targets) = manifest.get("target").and_then(toml::Value::as_table) { 4404 for target in targets.values().filter_map(toml::Value::as_table) { 4405 for section in ["dependencies", "dev-dependencies", "build-dependencies"] { 4406 if let Some(dependencies) = target.get(section).and_then(toml::Value::as_table) 4407 { 4408 collect_matching_dependencies(owner, dependencies, &matches, &mut found); 4409 } 4410 } 4411 } 4412 } 4413 } 4414 Ok(found) 4415 } 4416 4417 fn collect_matching_dependencies( 4418 owner: &str, 4419 dependencies: &toml::map::Map<String, toml::Value>, 4420 matches: &impl Fn(&str) -> bool, 4421 found: &mut BTreeSet<String>, 4422 ) { 4423 for (alias, specification) in dependencies { 4424 let package = specification 4425 .as_table() 4426 .and_then(|table| table.get("package")) 4427 .and_then(toml::Value::as_str) 4428 .unwrap_or(alias); 4429 if matches(package) { 4430 found.insert(format!("{owner}:{package}")); 4431 } 4432 } 4433 } 4434 4435 fn validate_changelog_release_notes( 4436 workspace_root: &Path, 4437 contract_version: &str, 4438 ) -> Result<(), String> { 4439 let path = workspace_root.join(CHANGELOG_RELATIVE); 4440 let raw = 4441 fs::read_to_string(&path).map_err(|error| format!("read {}: {error}", path.display()))?; 4442 let heading = format!("## [{contract_version}]"); 4443 let mut in_release = false; 4444 let mut has_release_note = false; 4445 for line in raw.lines() { 4446 let trimmed = line.trim(); 4447 if trimmed == heading { 4448 if in_release { 4449 return Err(format!( 4450 "{CHANGELOG_RELATIVE} contains duplicate heading {heading}" 4451 )); 4452 } 4453 in_release = true; 4454 continue; 4455 } 4456 if in_release && trimmed.starts_with("## [") { 4457 break; 4458 } 4459 if in_release && trimmed.starts_with("- ") && trimmed.len() > 2 { 4460 has_release_note = true; 4461 } 4462 } 4463 if !in_release { 4464 return Err(format!("{CHANGELOG_RELATIVE} is missing heading {heading}")); 4465 } 4466 if !has_release_note { 4467 return Err(format!( 4468 "{CHANGELOG_RELATIVE} release {contract_version} must contain at least one note" 4469 )); 4470 } 4471 Ok(()) 4472 } 4473 4474 fn validate_conformance_vector_mirrors(workspace_root: &Path) -> Result<(), String> { 4475 for (canonical_relative, mirror_relative) in CONFORMANCE_VECTOR_MIRRORS { 4476 let canonical = fs::read(workspace_root.join(canonical_relative)) 4477 .map_err(|error| format!("read {canonical_relative}: {error}"))?; 4478 let mirror = fs::read(workspace_root.join(mirror_relative)) 4479 .map_err(|error| format!("read {mirror_relative}: {error}"))?; 4480 if canonical != mirror { 4481 return Err(format!( 4482 "packaged conformance mirror {mirror_relative} must exactly match {canonical_relative}" 4483 )); 4484 } 4485 } 4486 Ok(()) 4487 } 4488 4489 fn validate_version_governance( 4490 bundle: &ContractBundle, 4491 workspace_root: &Path, 4492 ) -> Result<(), String> { 4493 validate_contract_version_lockstep(bundle)?; 4494 let version = bundle.manifest.contract.version.as_str(); 4495 validate_workspace_version_lockstep(workspace_root, version)?; 4496 validate_release_record(workspace_root, version, &bundle.version.semver)?; 4497 validate_conformance_vector_mirrors(workspace_root) 4498 } 4499 4500 fn collect_conformance_vector_paths(dir: &Path, paths: &mut Vec<PathBuf>) -> Result<(), String> { 4501 let read_dir = match fs::read_dir(dir) { 4502 Ok(read_dir) => read_dir, 4503 Err(e) => return Err(format!("read dir {}: {e}", dir.display())), 4504 }; 4505 let mut entries = read_dir.filter_map(Result::ok).collect::<Vec<_>>(); 4506 entries.sort_by_key(|entry| entry.file_name()); 4507 for entry in entries { 4508 let path = entry.path(); 4509 if path.is_dir() { 4510 collect_conformance_vector_paths(&path, paths)?; 4511 } else if path.extension().and_then(|ext| ext.to_str()) == Some("json") { 4512 paths.push(path); 4513 } 4514 } 4515 Ok(()) 4516 } 4517 4518 fn validate_conformance_vector_file( 4519 path: &Path, 4520 contract_version: &str, 4521 ) -> Result<ConformanceVectorFile, String> { 4522 let vector = parse_json::<ConformanceVectorFile>(path)?; 4523 if vector.suite.trim().is_empty() { 4524 return Err(format!( 4525 "conformance vector {} suite must not be empty", 4526 path.display() 4527 )); 4528 } 4529 if vector.vectors.is_empty() { 4530 return Err(format!( 4531 "conformance vector {} must contain at least one vector", 4532 path.display() 4533 )); 4534 } 4535 if vector.contract_version != base_contract_version(contract_version) { 4536 return Err(format!( 4537 "conformance vector {} version {} must match contract version {}", 4538 path.display(), 4539 vector.contract_version, 4540 base_contract_version(contract_version) 4541 )); 4542 } 4543 let mut ids = BTreeSet::new(); 4544 for entry in &vector.vectors { 4545 if entry.id.trim().is_empty() || entry.kind.trim().is_empty() { 4546 return Err(format!( 4547 "conformance vector {} entries must define non-empty id and kind", 4548 path.display() 4549 )); 4550 } 4551 if !ids.insert(entry.id.clone()) { 4552 return Err(format!( 4553 "conformance vector {} has duplicate vector id {}", 4554 path.display(), 4555 entry.id 4556 )); 4557 } 4558 match (&entry.expected, &entry.expected_error_contains) { 4559 (Some(_), None) => {} 4560 (None, Some(fragment)) if !fragment.trim().is_empty() => {} 4561 (None, Some(_)) => { 4562 return Err(format!( 4563 "conformance vector {} entry {} expected_error_contains must not be blank", 4564 path.display(), 4565 entry.id 4566 )); 4567 } 4568 _ => { 4569 return Err(format!( 4570 "conformance vector {} entry {} must define exactly one of expected or expected_error_contains", 4571 path.display(), 4572 entry.id 4573 )); 4574 } 4575 } 4576 } 4577 Ok(vector) 4578 } 4579 4580 fn validate_all_conformance_vectors( 4581 workspace_root: &Path, 4582 contract_version: &str, 4583 ) -> Result<(), String> { 4584 let vectors_dir = conformance_root(workspace_root).join("vectors"); 4585 if !vectors_dir.is_dir() { 4586 return validate_missing_conformance_vectors(workspace_root, &vectors_dir); 4587 } 4588 let mut paths = Vec::new(); 4589 collect_conformance_vector_paths(&vectors_dir, &mut paths)?; 4590 if paths.is_empty() { 4591 return Err(format!( 4592 "conformance vectors directory {} must contain JSON vectors", 4593 vectors_dir.display() 4594 )); 4595 } 4596 let canonical_comment_path = workspace_root.join(COMMENT_CONFORMANCE_VECTOR_RELATIVE); 4597 let canonical_deletion_path = workspace_root.join(DELETION_CONFORMANCE_VECTOR_RELATIVE); 4598 let canonical_deletion_suppression_path = 4599 workspace_root.join(DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE); 4600 for path in paths { 4601 let vector = validate_conformance_vector_file(&path, contract_version)?; 4602 validate_comment_vector_namespace(&path, &canonical_comment_path, &vector)?; 4603 validate_deletion_vector_namespace( 4604 &path, 4605 &canonical_deletion_path, 4606 &canonical_deletion_suppression_path, 4607 &vector, 4608 )?; 4609 } 4610 Ok(()) 4611 } 4612 4613 fn validate_deletion_vector_namespace( 4614 path: &Path, 4615 canonical_request_path: &Path, 4616 canonical_suppression_path: &Path, 4617 vector: &ConformanceVectorFile, 4618 ) -> Result<(), String> { 4619 if path == canonical_request_path || path == canonical_suppression_path { 4620 return Ok(()); 4621 } 4622 if let Some(entry) = vector 4623 .vectors 4624 .iter() 4625 .find(|entry| entry.kind.starts_with("social.deletion_request.")) 4626 { 4627 return Err(format!( 4628 "deletion conformance case kind {} in {} is outside canonical vectors {} and {}", 4629 entry.kind, 4630 path.display(), 4631 canonical_request_path.display(), 4632 canonical_suppression_path.display() 4633 )); 4634 } 4635 Ok(()) 4636 } 4637 4638 fn validate_comment_vector_namespace( 4639 path: &Path, 4640 canonical_path: &Path, 4641 vector: &ConformanceVectorFile, 4642 ) -> Result<(), String> { 4643 if path == canonical_path { 4644 return Ok(()); 4645 } 4646 if let Some(entry) = vector 4647 .vectors 4648 .iter() 4649 .find(|entry| entry.kind.starts_with("social.comment.")) 4650 { 4651 return Err(format!( 4652 "comment conformance case kind {} in {} is outside canonical vector {}", 4653 entry.kind, 4654 path.display(), 4655 canonical_path.display() 4656 )); 4657 } 4658 Ok(()) 4659 } 4660 4661 pub fn write_knowledge_contract_manifest(workspace_root: &Path) -> Result<(), String> { 4662 let manifest_json = write_knowledge_contract_manifest_artifacts(workspace_root)?; 4663 validate_knowledge_contract_manifest_context(workspace_root, &manifest_json) 4664 } 4665 4666 pub fn validate_knowledge_contract_manifest(workspace_root: &Path) -> Result<(), String> { 4667 let manifest_json = validate_knowledge_contract_manifest_artifacts(workspace_root)?; 4668 validate_knowledge_contract_manifest_context(workspace_root, &manifest_json) 4669 } 4670 4671 fn write_knowledge_contract_manifest_artifacts(workspace_root: &Path) -> Result<String, String> { 4672 with_artifact_bundle_transaction(workspace_root, |transaction| { 4673 let manifest_json = expected_knowledge_contract_manifest_json()?; 4674 let manifest_sha256 = expected_knowledge_contract_manifest_sha256()?; 4675 transaction.write(vec![ 4676 GeneratedArtifact { 4677 relative: KNOWLEDGE_MANIFEST_RELATIVE, 4678 contents: manifest_json.into_bytes(), 4679 }, 4680 GeneratedArtifact { 4681 relative: KNOWLEDGE_MANIFEST_SHA256_RELATIVE, 4682 contents: format!("{manifest_sha256}\n").into_bytes(), 4683 }, 4684 ])?; 4685 validate_knowledge_contract_manifest_artifacts_under_lock(workspace_root) 4686 }) 4687 } 4688 4689 fn validate_knowledge_contract_manifest_artifacts(workspace_root: &Path) -> Result<String, String> { 4690 with_artifact_bundle_transaction(workspace_root, |_| { 4691 validate_knowledge_contract_manifest_artifacts_under_lock(workspace_root) 4692 }) 4693 } 4694 4695 fn validate_knowledge_contract_manifest_artifacts_under_lock( 4696 workspace_root: &Path, 4697 ) -> Result<String, String> { 4698 let expected_json = expected_knowledge_contract_manifest_json()?; 4699 let expected_sha256 = expected_knowledge_contract_manifest_sha256()?; 4700 let actual_json = read_regular_file(workspace_root, KNOWLEDGE_MANIFEST_RELATIVE)?; 4701 let actual_sha256 = read_regular_file(workspace_root, KNOWLEDGE_MANIFEST_SHA256_RELATIVE)?; 4702 let actual_json_text = std::str::from_utf8(&actual_json) 4703 .map_err(|error| format!("{KNOWLEDGE_MANIFEST_RELATIVE} must be UTF-8 JSON: {error}"))?; 4704 let parsed = 4705 radroots_event_codec::manifest::parse_knowledge_contract_manifest_json(actual_json_text) 4706 .map_err(|error| format!("parse {KNOWLEDGE_MANIFEST_RELATIVE}: {error}"))?; 4707 4708 validate_knowledge_contract_manifest_shape(&parsed)?; 4709 validate_canonical_json_artifact(KNOWLEDGE_MANIFEST_RELATIVE, &actual_json)?; 4710 validate_sha256_artifact(KNOWLEDGE_MANIFEST_SHA256_RELATIVE, &actual_sha256)?; 4711 4712 if actual_json != expected_json.as_bytes() { 4713 return Err(stale_knowledge_manifest_error(KNOWLEDGE_MANIFEST_RELATIVE)); 4714 } 4715 if actual_sha256 != format!("{expected_sha256}\n").as_bytes() { 4716 return Err(stale_knowledge_manifest_error( 4717 KNOWLEDGE_MANIFEST_SHA256_RELATIVE, 4718 )); 4719 } 4720 if parsed != radroots_event_codec::manifest::knowledge_contract_manifest() { 4721 return Err(stale_knowledge_manifest_error(KNOWLEDGE_MANIFEST_RELATIVE)); 4722 } 4723 4724 Ok(actual_json_text.to_owned()) 4725 } 4726 4727 fn expected_knowledge_contract_manifest_json() -> Result<String, String> { 4728 let expected_json = radroots_event_codec::manifest::contract_manifest_json() 4729 .map_err(|error| format!("serialize knowledge contract manifest: {error}"))?; 4730 Ok(expected_json) 4731 } 4732 4733 fn expected_knowledge_contract_manifest_sha256() -> Result<String, String> { 4734 radroots_event_codec::manifest::contract_manifest_sha256() 4735 .map_err(|error| format!("hash knowledge contract manifest: {error}")) 4736 } 4737 4738 fn validate_knowledge_contract_manifest_shape( 4739 manifest: &radroots_event_codec::manifest::RadrootsKnowledgeContractManifest, 4740 ) -> Result<(), String> { 4741 if manifest.schema_version 4742 != radroots_event_codec::manifest::RADROOTS_KNOWLEDGE_CONTRACT_MANIFEST_SCHEMA_VERSION 4743 { 4744 return Err(format!( 4745 "{KNOWLEDGE_MANIFEST_RELATIVE} schema_version must be {}", 4746 radroots_event_codec::manifest::RADROOTS_KNOWLEDGE_CONTRACT_MANIFEST_SCHEMA_VERSION 4747 )); 4748 } 4749 if manifest.registry_version 4750 != radroots_event_codec::manifest::registry_v7::RADROOTS_EVENT_CONTRACT_REGISTRY_V7_VERSION 4751 { 4752 return Err(format!( 4753 "{KNOWLEDGE_MANIFEST_RELATIVE} registry_version must be {}", 4754 radroots_event_codec::manifest::registry_v7::RADROOTS_EVENT_CONTRACT_REGISTRY_V7_VERSION 4755 )); 4756 } 4757 if manifest.contract_count != manifest.contracts.len() { 4758 return Err(format!( 4759 "{KNOWLEDGE_MANIFEST_RELATIVE} contract_count must match its contract inventory" 4760 )); 4761 } 4762 if manifest 4763 .contracts 4764 .windows(2) 4765 .any(|pair| pair[0].contract_id >= pair[1].contract_id) 4766 { 4767 return Err(format!( 4768 "{KNOWLEDGE_MANIFEST_RELATIVE} contract IDs must be unique and strictly sorted" 4769 )); 4770 } 4771 Ok(()) 4772 } 4773 4774 fn stale_knowledge_manifest_error(relative: &str) -> String { 4775 format!("{relative} is stale; run `{KNOWLEDGE_MANIFEST_WRITE_COMMAND}`") 4776 } 4777 4778 fn validate_knowledge_contract_manifest_context( 4779 workspace_root: &Path, 4780 manifest_json: &str, 4781 ) -> Result<(), String> { 4782 validate_knowledge_manifest_witnesses(workspace_root, manifest_json)?; 4783 validate_knowledge_conformance_vector_inventory(workspace_root)?; 4784 4785 let bundle = load_contract_bundle(workspace_root)?; 4786 let knowledge_manifest_vector = validate_conformance_vector_file( 4787 &workspace_root.join(KNOWLEDGE_MANIFEST_AND_DECODE_RELATIVE), 4788 &bundle.manifest.contract.version, 4789 )?; 4790 validate_knowledge_manifest_vector_semantics(manifest_json, &knowledge_manifest_vector)?; 4791 validate_conformance_vector_file( 4792 &workspace_root.join(KNOWLEDGE_PUBLIC_SURFACE_RELATIVE), 4793 &bundle.manifest.contract.version, 4794 )?; 4795 Ok(()) 4796 } 4797 4798 fn validate_knowledge_manifest_vector_semantics( 4799 manifest_json: &str, 4800 vector: &ConformanceVectorFile, 4801 ) -> Result<(), String> { 4802 let manifest = serde_json::from_str::<Value>(manifest_json) 4803 .map_err(|error| format!("parse knowledge manifest JSON: {error}"))?; 4804 let schema_version = manifest 4805 .get("schema_version") 4806 .and_then(Value::as_u64) 4807 .ok_or_else(|| { 4808 "knowledge manifest schema_version must be an unsigned integer".to_string() 4809 })?; 4810 let registry_version = manifest 4811 .get("registry_version") 4812 .and_then(Value::as_u64) 4813 .ok_or_else(|| { 4814 "knowledge manifest registry_version must be an unsigned integer".to_string() 4815 })?; 4816 let case = vector 4817 .vectors 4818 .iter() 4819 .find(|entry| entry.id == "knowledge_manifest_fields_valid_001") 4820 .ok_or_else(|| { 4821 "knowledge manifest conformance must define knowledge_manifest_fields_valid_001" 4822 .to_string() 4823 })?; 4824 if case.kind != "knowledge.contract_manifest_json.valid" { 4825 return Err(format!( 4826 "knowledge manifest conformance case kind drift: expected knowledge.contract_manifest_json.valid, got {}", 4827 case.kind 4828 )); 4829 } 4830 4831 let expected_registry_marker = format!("radroots_event_contract_registry_v{registry_version}"); 4832 let actual_registry_marker = case.input.get("registry").and_then(Value::as_str); 4833 if actual_registry_marker != Some(expected_registry_marker.as_str()) { 4834 return Err(format!( 4835 "knowledge manifest conformance registry marker drift: expected {expected_registry_marker}, got {}", 4836 actual_registry_marker.unwrap_or("<missing-or-non-string>") 4837 )); 4838 } 4839 4840 for (field, expected) in [ 4841 ("schema_version", schema_version), 4842 ("registry_version", registry_version), 4843 ] { 4844 let actual = case.expected_value()?.get(field).and_then(Value::as_u64); 4845 if actual != Some(expected) { 4846 return Err(format!( 4847 "knowledge manifest conformance expected {field} drift: expected {expected}, got {}", 4848 actual 4849 .map(|value| value.to_string()) 4850 .unwrap_or_else(|| "<missing-or-non-integer>".to_string()) 4851 )); 4852 } 4853 } 4854 Ok(()) 4855 } 4856 4857 fn validate_knowledge_conformance_vector_inventory(workspace_root: &Path) -> Result<(), String> { 4858 let expected = BTreeSet::from([ 4859 KNOWLEDGE_MANIFEST_AND_DECODE_RELATIVE.to_owned(), 4860 KNOWLEDGE_PUBLIC_SURFACE_RELATIVE.to_owned(), 4861 ]); 4862 let mut paths = Vec::new(); 4863 collect_conformance_vector_paths( 4864 &workspace_root.join("contracts/conformance/vectors/knowledge"), 4865 &mut paths, 4866 )?; 4867 let mut actual = BTreeSet::new(); 4868 for path in paths { 4869 let relative = path.strip_prefix(workspace_root).map_err(|error| { 4870 format!( 4871 "knowledge conformance vector {} is outside workspace root {}: {error}", 4872 path.display(), 4873 workspace_root.display() 4874 ) 4875 })?; 4876 actual.insert(relative.to_string_lossy().replace('\\', "/")); 4877 } 4878 if actual != expected { 4879 return Err(format!( 4880 "knowledge conformance vector inventory mismatch: expected {:?}, found {:?}", 4881 expected, actual 4882 )); 4883 } 4884 Ok(()) 4885 } 4886 4887 fn validate_knowledge_manifest_witnesses( 4888 workspace_root: &Path, 4889 actual_json: &str, 4890 ) -> Result<(), String> { 4891 for relative in legacy_knowledge_manifest_relatives() { 4892 let path = workspace_root.join(&relative); 4893 if path.exists() { 4894 return Err(format!( 4895 "stale knowledge manifest artifact remains at {}", 4896 relative 4897 )); 4898 } 4899 } 4900 4901 let value = serde_json::from_str::<Value>(actual_json) 4902 .map_err(|error| format!("parse knowledge manifest JSON: {error}"))?; 4903 if value.get("schema_version").and_then(Value::as_u64) != Some(2) { 4904 return Err("knowledge manifest schema_version must be 2".to_string()); 4905 } 4906 let contracts = value 4907 .get("contracts") 4908 .and_then(Value::as_array) 4909 .ok_or_else(|| "knowledge manifest contracts must be an array".to_string())?; 4910 let mut previous_id: Option<String> = None; 4911 let mut ids = BTreeSet::new(); 4912 4913 for contract in contracts { 4914 let contract_id = contract 4915 .get("contract_id") 4916 .and_then(Value::as_str) 4917 .ok_or_else(|| "knowledge manifest entry missing contract_id".to_string())?; 4918 if let Some(previous) = previous_id.as_deref() 4919 && previous > contract_id 4920 { 4921 return Err("knowledge manifest contracts must be sorted by contract_id".to_string()); 4922 } 4923 previous_id = Some(contract_id.to_string()); 4924 if !ids.insert(contract_id.to_string()) { 4925 return Err(format!( 4926 "knowledge manifest has duplicate contract id {contract_id}" 4927 )); 4928 } 4929 4930 if contract.get("stability").and_then(Value::as_str) != Some("experimental") { 4931 return Err(format!( 4932 "knowledge manifest contract {contract_id} must be experimental" 4933 )); 4934 } 4935 if contract_id == "radroots.wiki.merge_request.v1" 4936 && contract.get("content_schema").and_then(Value::as_str) != Some("plain_text") 4937 { 4938 return Err( 4939 "wiki merge request manifest content_schema must be plain_text".to_string(), 4940 ); 4941 } 4942 4943 let sdk_builder_support = manifest_bool_field(contract, "sdk_builder_support")?; 4944 let sdk_draft_support = manifest_bool_field(contract, "sdk_draft_support")?; 4945 let wasm_tag_builder_support = manifest_bool_field(contract, "wasm_tag_builder_support")?; 4946 let wasm_verified_decode_support = 4947 manifest_bool_field(contract, "wasm_verified_decode_support")?; 4948 4949 if KNOWLEDGE_MVP_SUPPORT_CONTRACT_IDS.contains(&contract_id) 4950 && (!sdk_builder_support || !sdk_draft_support || !wasm_tag_builder_support) 4951 { 4952 return Err(format!( 4953 "knowledge manifest MVP contract {contract_id} must report SDK and WASM tag support" 4954 )); 4955 } 4956 if KNOWLEDGE_BETA_CONTRACT_IDS.contains(&contract_id) 4957 && (sdk_builder_support || sdk_draft_support || wasm_tag_builder_support) 4958 { 4959 return Err(format!( 4960 "knowledge manifest beta contract {contract_id} must not overclaim builder support" 4961 )); 4962 } 4963 if !wasm_verified_decode_support { 4964 return Err(format!( 4965 "knowledge manifest contract {contract_id} must report WASM verified decode support" 4966 )); 4967 } 4968 } 4969 4970 for contract_id in KNOWLEDGE_MVP_SUPPORT_CONTRACT_IDS 4971 .iter() 4972 .chain(KNOWLEDGE_BETA_CONTRACT_IDS.iter()) 4973 { 4974 if !ids.contains(*contract_id) { 4975 return Err(format!( 4976 "knowledge manifest missing required contract {contract_id}" 4977 )); 4978 } 4979 } 4980 4981 Ok(()) 4982 } 4983 4984 fn legacy_knowledge_manifest_relatives() -> [String; 2] { 4985 [ 4986 KNOWLEDGE_MANIFEST_RELATIVE.replace(".v2.", ".v1."), 4987 KNOWLEDGE_MANIFEST_SHA256_RELATIVE.replace(".v2.", ".v1."), 4988 ] 4989 } 4990 4991 fn manifest_bool_field(contract: &Value, field: &str) -> Result<bool, String> { 4992 contract 4993 .get(field) 4994 .and_then(Value::as_bool) 4995 .ok_or_else(|| format!("knowledge manifest entry missing boolean field {field}")) 4996 } 4997 4998 #[cfg(not(test))] 4999 fn validate_missing_conformance_vectors( 5000 _workspace_root: &Path, 5001 vectors_dir: &Path, 5002 ) -> Result<(), String> { 5003 Err(format!( 5004 "conformance vectors directory {} must exist", 5005 vectors_dir.display() 5006 )) 5007 } 5008 5009 #[cfg(test)] 5010 #[cfg_attr(coverage_nightly, coverage(off))] 5011 fn validate_missing_conformance_vectors( 5012 _workspace_root: &Path, 5013 _vectors_dir: &Path, 5014 ) -> Result<(), String> { 5015 Ok(()) 5016 } 5017 5018 #[derive(Debug)] 5019 struct WorkspacePackageRecord { 5020 name: String, 5021 #[cfg_attr(not(test), allow(dead_code))] 5022 manifest_path: PathBuf, 5023 publish_enabled: bool, 5024 publish: Option<PackagePublish>, 5025 manifest_value: toml::Value, 5026 } 5027 5028 fn workspace_package_records(workspace_root: &Path) -> Result<Vec<WorkspacePackageRecord>, String> { 5029 let workspace_manifest = 5030 parse_toml::<WorkspaceCargoManifest>(&workspace_root.join("Cargo.toml"))?; 5031 let mut records = Vec::with_capacity(workspace_manifest.workspace.members.len()); 5032 for member in workspace_manifest.workspace.members { 5033 let manifest_path = workspace_root.join(&member).join("Cargo.toml"); 5034 let raw = match fs::read_to_string(&manifest_path) { 5035 Ok(raw) => raw, 5036 Err(e) => return Err(format!("read {}: {e}", manifest_path.display())), 5037 }; 5038 let manifest_value = match toml::from_str::<toml::Value>(&raw) { 5039 Ok(value) => value, 5040 Err(e) => return Err(format!("parse {}: {e}", manifest_path.display())), 5041 }; 5042 let package_manifest = match toml::from_str::<PackageCargoManifest>(&raw) { 5043 Ok(manifest) => manifest, 5044 Err(e) => return Err(format!("parse {}: {e}", manifest_path.display())), 5045 }; 5046 let name = package_manifest.package.name; 5047 let publish_enabled = package_publish_enabled(package_manifest.package.publish.as_ref()); 5048 let publish = package_manifest.package.publish.clone(); 5049 records.push(WorkspacePackageRecord { 5050 name, 5051 manifest_path, 5052 publish_enabled, 5053 publish, 5054 manifest_value, 5055 }); 5056 } 5057 Ok(records) 5058 } 5059 5060 fn workspace_package_names(workspace_root: &Path) -> Result<Vec<String>, String> { 5061 Ok(workspace_package_records(workspace_root)? 5062 .into_iter() 5063 .map(|record| record.name) 5064 .collect()) 5065 } 5066 5067 fn coverage_required_workspace_crates(workspace_root: &Path) -> Result<BTreeSet<String>, String> { 5068 let names = workspace_package_names(workspace_root)? 5069 .into_iter() 5070 .filter(|crate_name| !coverage_policy_excludes_workspace_crate(crate_name)) 5071 .collect::<Vec<_>>(); 5072 collect_unique_set(&names, "workspace coverage crates") 5073 } 5074 5075 fn coverage_policy_excludes_workspace_crate(crate_name: &str) -> bool { 5076 crate_name.contains("_simplex_") || crate_name.starts_with("simplex_") 5077 } 5078 5079 #[cfg_attr(not(test), allow(dead_code))] 5080 fn workspace_package_manifests(workspace_root: &Path) -> Result<BTreeMap<String, PathBuf>, String> { 5081 let mut manifests = BTreeMap::new(); 5082 for record in workspace_package_records(workspace_root)? { 5083 if manifests 5084 .insert(record.name, record.manifest_path) 5085 .is_some() 5086 { 5087 return Err("duplicate workspace package name in manifest map".to_string()); 5088 } 5089 } 5090 Ok(manifests) 5091 } 5092 5093 fn load_coverage_policy( 5094 contract_root: &Path, 5095 ) -> Result<crate::coverage::CoveragePolicyFile, String> { 5096 read_coverage_policy(&coverage_root(contract_root).join("coverage.toml")) 5097 } 5098 5099 fn coverage_root(contract_root: &Path) -> PathBuf { 5100 contract_root.to_path_buf() 5101 } 5102 5103 fn release_contract_path(workspace_root: &Path, _contract_version: &str) -> PathBuf { 5104 workspace_root.join(RELEASE_POLICY_RELATIVE) 5105 } 5106 5107 #[cfg(test)] 5108 fn root_release_policy_path(workspace_root: &Path) -> PathBuf { 5109 release_contract_path(workspace_root, "1.0.0") 5110 } 5111 5112 fn resolve_release_contract_path_with_override( 5113 workspace_root: &Path, 5114 contract_version: &str, 5115 release_policy_override: Option<PathBuf>, 5116 ) -> Result<PathBuf, String> { 5117 if let Some(path) = release_policy_override { 5118 if !path.is_file() { 5119 return Err(format!( 5120 "release policy override points to a missing file: {}", 5121 path.display() 5122 )); 5123 } 5124 return Ok(path); 5125 } 5126 5127 let path = release_contract_path(workspace_root, contract_version); 5128 if !path.is_file() { 5129 return Err(format!( 5130 "release publish policy not found; expected {}", 5131 path.display() 5132 )); 5133 } 5134 5135 Ok(path) 5136 } 5137 5138 #[cfg(test)] 5139 fn load_release_contract( 5140 workspace_root: &Path, 5141 contract_version: &str, 5142 ) -> Result<ReleaseContractFile, String> { 5143 load_release_contract_with_override(workspace_root, contract_version, None) 5144 } 5145 5146 fn load_release_contract_with_override( 5147 workspace_root: &Path, 5148 contract_version: &str, 5149 release_policy_override: Option<PathBuf>, 5150 ) -> Result<ReleaseContractFile, String> { 5151 let path = resolve_release_contract_path_with_override( 5152 workspace_root, 5153 contract_version, 5154 release_policy_override, 5155 )?; 5156 parse_toml::<ReleaseContractFile>(&path) 5157 } 5158 5159 #[cfg(test)] 5160 #[cfg_attr(coverage_nightly, coverage(off))] 5161 fn should_synthesize_owner_contracts_for_tests(workspace_root: &Path) -> bool { 5162 workspace_root 5163 .join("crates") 5164 .join("core") 5165 .join("Cargo.toml") 5166 .is_file() 5167 && workspace_root 5168 .join("crates") 5169 .join("event_codec") 5170 .join("Cargo.toml") 5171 .is_file() 5172 && workspace_root 5173 .join("crates") 5174 .join("trade") 5175 .join("Cargo.toml") 5176 .is_file() 5177 && workspace_root 5178 .join("contracts") 5179 .join("manifest.toml") 5180 .is_file() 5181 && workspace_root 5182 .join("contracts") 5183 .join("coverage.toml") 5184 .is_file() 5185 } 5186 5187 fn package_publish_enabled(publish: Option<&PackagePublish>) -> bool { 5188 match publish { 5189 None => true, 5190 Some(PackagePublish::Bool(flag)) => *flag, 5191 Some(PackagePublish::Registries(registries)) => !registries.is_empty(), 5192 } 5193 } 5194 5195 #[cfg_attr(not(test), allow(dead_code))] 5196 fn workspace_package_publish_flags( 5197 workspace_root: &Path, 5198 ) -> Result<BTreeMap<String, bool>, String> { 5199 let mut flags = BTreeMap::new(); 5200 for record in workspace_package_records(workspace_root)? { 5201 if flags 5202 .insert(record.name.clone(), record.publish_enabled) 5203 .is_some() 5204 { 5205 return Err(format!("duplicate workspace package name {}", record.name)); 5206 } 5207 } 5208 Ok(flags) 5209 } 5210 5211 fn workspace_package_publish_configs( 5212 workspace_root: &Path, 5213 ) -> Result<BTreeMap<String, Option<PackagePublish>>, String> { 5214 let mut configs = BTreeMap::new(); 5215 for record in workspace_package_records(workspace_root)? { 5216 if configs 5217 .insert(record.name.clone(), record.publish.clone()) 5218 .is_some() 5219 { 5220 return Err(format!("duplicate workspace package name {}", record.name)); 5221 } 5222 } 5223 Ok(configs) 5224 } 5225 5226 fn read_workspace_package_dependencies( 5227 workspace_root: &Path, 5228 ) -> Result<BTreeMap<String, BTreeSet<String>>, String> { 5229 let package_records = workspace_package_records(workspace_root)?; 5230 let workspace_names = package_records 5231 .iter() 5232 .map(|record| record.name.clone()) 5233 .collect::<BTreeSet<_>>(); 5234 5235 let mut deps = BTreeMap::new(); 5236 for record in package_records { 5237 let mut package_deps = BTreeSet::new(); 5238 for section in ["dependencies", "build-dependencies"] { 5239 let Some(table) = record 5240 .manifest_value 5241 .get(section) 5242 .and_then(toml::Value::as_table) 5243 else { 5244 continue; 5245 }; 5246 for dep_name in table.keys() { 5247 if workspace_names.contains(dep_name) { 5248 package_deps.insert(dep_name.clone()); 5249 } 5250 } 5251 } 5252 deps.insert(record.name, package_deps); 5253 } 5254 5255 Ok(deps) 5256 } 5257 5258 fn validate_publishable_dto_tooling_sources( 5259 workspace_root: &Path, 5260 public_crates: &BTreeSet<String>, 5261 ) -> Result<(), String> { 5262 let workspace_manifest_value = parse_toml::<toml::Value>(&workspace_root.join("Cargo.toml"))?; 5263 let package_records = workspace_package_records(workspace_root)?; 5264 5265 for record in package_records { 5266 if !public_crates.contains(&record.name) { 5267 continue; 5268 } 5269 for section in ["dependencies", "build-dependencies"] { 5270 let Some(dependencies) = record 5271 .manifest_value 5272 .get(section) 5273 .and_then(toml::Value::as_table) 5274 else { 5275 continue; 5276 }; 5277 for dependency_name in DTO_TOOLING_DEPENDENCIES { 5278 let Some(dependency_value) = dependencies.get(dependency_name) else { 5279 continue; 5280 }; 5281 validate_publishable_dto_dependency_source( 5282 workspace_manifest_value.as_table(), 5283 record.name.as_str(), 5284 section, 5285 dependency_name, 5286 dependency_value, 5287 )?; 5288 } 5289 } 5290 } 5291 5292 Ok(()) 5293 } 5294 5295 fn validate_publishable_dto_dependency_source( 5296 workspace_manifest: Option<&toml::value::Table>, 5297 crate_name: &str, 5298 section: &str, 5299 dependency_name: &str, 5300 dependency_value: &toml::Value, 5301 ) -> Result<(), String> { 5302 let resolved = 5303 resolve_workspace_dependency_source(workspace_manifest, dependency_name, dependency_value) 5304 .unwrap_or(dependency_value); 5305 if dependency_has_source_key(resolved, "git") { 5306 return Err(format!( 5307 "public crate {crate_name} {section}.{dependency_name} must use a crates.io DTO tooling dependency, not a git source" 5308 )); 5309 } 5310 if dependency_has_source_key(resolved, "path") { 5311 return Err(format!( 5312 "public crate {crate_name} {section}.{dependency_name} must use a crates.io DTO tooling dependency, not a path source" 5313 )); 5314 } 5315 Ok(()) 5316 } 5317 5318 fn resolve_workspace_dependency_source<'a>( 5319 workspace_manifest: Option<&'a toml::value::Table>, 5320 dependency_name: &str, 5321 dependency_value: &toml::Value, 5322 ) -> Option<&'a toml::Value> { 5323 if !dependency_has_workspace_true(dependency_value) { 5324 return None; 5325 } 5326 workspace_manifest? 5327 .get("workspace")? 5328 .as_table()? 5329 .get("dependencies")? 5330 .as_table()? 5331 .get(dependency_name) 5332 } 5333 5334 fn dependency_has_workspace_true(value: &toml::Value) -> bool { 5335 value 5336 .as_table() 5337 .and_then(|table| table.get("workspace")) 5338 .and_then(toml::Value::as_bool) 5339 .unwrap_or(false) 5340 } 5341 5342 fn dependency_has_source_key(value: &toml::Value, key: &str) -> bool { 5343 value.as_table().and_then(|table| table.get(key)).is_some() 5344 } 5345 5346 fn join_set(items: &BTreeSet<String>) -> String { 5347 items.iter().cloned().collect::<Vec<_>>().join(", ") 5348 } 5349 5350 fn collect_unique_set(items: &[String], field: &str) -> Result<BTreeSet<String>, String> { 5351 let mut set = BTreeSet::new(); 5352 for item in items { 5353 if item.trim().is_empty() { 5354 return Err(format!("{field} contains an empty crate name")); 5355 } 5356 if !set.insert(item.clone()) { 5357 return Err(format!("{field} has duplicate crate {}", item)); 5358 } 5359 } 5360 Ok(set) 5361 } 5362 5363 fn collect_non_empty_set(items: &[String], field: &str) -> Result<BTreeSet<String>, String> { 5364 let mut set = BTreeSet::new(); 5365 for item in items { 5366 if item.trim().is_empty() { 5367 return Err(format!("{field} contains an empty value")); 5368 } 5369 if !set.insert(item.clone()) { 5370 return Err(format!("{field} has duplicate value {}", item)); 5371 } 5372 } 5373 Ok(set) 5374 } 5375 5376 fn validate_crate_identifier(value: &str, field: &str) -> Result<(), String> { 5377 let trimmed = value.trim(); 5378 if trimmed.is_empty() { 5379 return Err(format!("{field} is required")); 5380 } 5381 if trimmed != value 5382 || trimmed.contains('/') 5383 || trimmed.contains('\\') 5384 || trimmed.contains("..") 5385 || trimmed == "radroots_sdk" 5386 { 5387 return Err(format!("{field} must be a crate identifier")); 5388 } 5389 Ok(()) 5390 } 5391 5392 fn validate_surface_metadata(surface: &Surface) -> Result<(), String> { 5393 if let Some(tiers) = &surface.rust_crate_tiers { 5394 let mut tier_crates = BTreeSet::new(); 5395 for (field, crates) in [ 5396 ( 5397 "surface.rust_crate_tiers.advanced_substrate", 5398 &tiers.advanced_substrate, 5399 ), 5400 ( 5401 "surface.rust_crate_tiers.published_support", 5402 &tiers.published_support, 5403 ), 5404 ( 5405 "surface.rust_crate_tiers.deferred_publication", 5406 &tiers.deferred_publication, 5407 ), 5408 ] { 5409 let entries = collect_unique_set(crates, field)?; 5410 if entries.is_empty() { 5411 return Err(format!("{field} must not be empty")); 5412 } 5413 for crate_name in entries { 5414 if !tier_crates.insert(crate_name.clone()) { 5415 return Err(format!( 5416 "surface.rust_crate_tiers has duplicate crate {crate_name}" 5417 )); 5418 } 5419 } 5420 } 5421 } 5422 5423 if let Some(replica) = &surface.internal_replica_crates { 5424 validate_crate_identifier(&replica.schema, "surface.internal_replica_crates.schema")?; 5425 validate_crate_identifier(&replica.storage, "surface.internal_replica_crates.storage")?; 5426 validate_crate_identifier(&replica.sync, "surface.internal_replica_crates.sync")?; 5427 } 5428 5429 Ok(()) 5430 } 5431 5432 fn validate_policy_metadata(policy: &Policy) -> Result<(), String> { 5433 if !policy.exclude_internal_workspace_crates 5434 || !policy.require_reproducible_exports 5435 || !policy.require_conformance_vectors 5436 { 5437 return Err("contract policy flags must all be true".to_string()); 5438 } 5439 if let Some(replica) = &policy.replica 5440 && (!replica.forbid_legacy_alias_identifiers 5441 || !replica.require_transport_agnostic_sync_contract 5442 || !replica.require_deterministic_emit_ingest) 5443 { 5444 return Err("contract replica policy flags must all be true".to_string()); 5445 } 5446 Ok(()) 5447 } 5448 5449 fn parse_replica_transfer_constant(path: &Path, name: &str) -> Result<u32, String> { 5450 let source = 5451 fs::read_to_string(path).map_err(|error| format!("read {}: {error}", path.display()))?; 5452 let declaration_prefix = format!("pub const {name}: u32 ="); 5453 let mut value = None; 5454 for line in source.lines() { 5455 let Some(raw_value) = line.trim().strip_prefix(&declaration_prefix) else { 5456 continue; 5457 }; 5458 let raw_value = raw_value.trim().strip_suffix(';').ok_or_else(|| { 5459 format!( 5460 "replica transfer constant {name} in {} must terminate with a semicolon", 5461 path.display() 5462 ) 5463 })?; 5464 let parsed = raw_value.parse::<u32>().map_err(|error| { 5465 format!( 5466 "replica transfer constant {name} in {} must be a u32 literal: {error}", 5467 path.display() 5468 ) 5469 })?; 5470 if value.replace(parsed).is_some() { 5471 return Err(format!( 5472 "replica transfer constant {name} must be declared exactly once in {}", 5473 path.display() 5474 )); 5475 } 5476 } 5477 value.ok_or_else(|| { 5478 format!( 5479 "replica transfer constant {name} is missing from {}", 5480 path.display() 5481 ) 5482 }) 5483 } 5484 5485 fn has_exact_legacy_ingest_cfg(attributes: &[syn::Attribute]) -> bool { 5486 let mut cfg_attributes = attributes 5487 .iter() 5488 .filter(|attribute| attribute.path().is_ident("cfg")); 5489 let Some(attribute) = cfg_attributes.next() else { 5490 return false; 5491 }; 5492 if cfg_attributes.next().is_some() { 5493 return false; 5494 } 5495 let syn::Meta::List(arguments) = &attribute.meta else { 5496 return false; 5497 }; 5498 let Ok(predicate) = arguments.parse_args::<syn::Meta>() else { 5499 return false; 5500 }; 5501 let syn::Meta::NameValue(feature) = predicate else { 5502 return false; 5503 }; 5504 if !feature.path.is_ident("feature") { 5505 return false; 5506 } 5507 matches!( 5508 feature.value, 5509 syn::Expr::Lit(syn::ExprLit { 5510 lit: syn::Lit::Str(value), 5511 .. 5512 }) if value.value() == "legacy-ingest" 5513 ) 5514 } 5515 5516 fn replica_use_tree_references_ingest(tree: &syn::UseTree) -> bool { 5517 match tree { 5518 syn::UseTree::Path(path) => { 5519 path.ident == "ingest" || replica_use_tree_references_ingest(&path.tree) 5520 } 5521 syn::UseTree::Name(name) => name.ident == "ingest", 5522 syn::UseTree::Rename(rename) => rename.ident == "ingest", 5523 syn::UseTree::Group(group) => group.items.iter().any(replica_use_tree_references_ingest), 5524 syn::UseTree::Glob(_) => false, 5525 } 5526 } 5527 5528 fn collect_public_replica_ingest_exports<'a>( 5529 items: &'a [syn::Item], 5530 exports: &mut Vec<&'a syn::ItemUse>, 5531 ) { 5532 for item in items { 5533 match item { 5534 syn::Item::Use(export) 5535 if matches!(&export.vis, syn::Visibility::Public(_)) 5536 && replica_use_tree_references_ingest(&export.tree) => 5537 { 5538 exports.push(export); 5539 } 5540 syn::Item::Mod(module) if matches!(&module.vis, syn::Visibility::Public(_)) => { 5541 if let Some((_, nested_items)) = &module.content { 5542 collect_public_replica_ingest_exports(nested_items, exports); 5543 } 5544 } 5545 _ => {} 5546 } 5547 } 5548 } 5549 5550 fn validate_replica_legacy_ingest_exports(lib_path: &Path, source: &str) -> Result<(), String> { 5551 let syntax = syn::parse_file(source) 5552 .map_err(|error| format!("parse replica sync source {}: {error}", lib_path.display()))?; 5553 let ingest_modules = syntax 5554 .items 5555 .iter() 5556 .filter_map(|item| match item { 5557 syn::Item::Mod(module) if module.ident == "ingest" => Some(module), 5558 _ => None, 5559 }) 5560 .collect::<Vec<_>>(); 5561 if ingest_modules.len() != 1 { 5562 return Err(format!( 5563 "replica legacy ingest source {} must declare exactly one ingest module", 5564 lib_path.display() 5565 )); 5566 } 5567 let ingest_module = ingest_modules[0]; 5568 if !matches!(&ingest_module.vis, syn::Visibility::Public(_)) { 5569 return Err(format!( 5570 "replica legacy ingest module in {} must remain public", 5571 lib_path.display() 5572 )); 5573 } 5574 if !has_exact_legacy_ingest_cfg(&ingest_module.attrs) { 5575 return Err(format!( 5576 "replica legacy ingest module in {} must be guarded by exact #[cfg(feature = \"legacy-ingest\")]", 5577 lib_path.display() 5578 )); 5579 } 5580 5581 let mut ingest_exports = Vec::new(); 5582 collect_public_replica_ingest_exports(&syntax.items, &mut ingest_exports); 5583 if ingest_exports.is_empty() { 5584 return Err(format!( 5585 "replica legacy ingest source {} must publicly re-export the ingest API", 5586 lib_path.display() 5587 )); 5588 } 5589 if ingest_exports 5590 .iter() 5591 .any(|export| !has_exact_legacy_ingest_cfg(&export.attrs)) 5592 { 5593 return Err(format!( 5594 "every public replica ingest re-export in {} must be guarded by exact #[cfg(feature = \"legacy-ingest\")]", 5595 lib_path.display() 5596 )); 5597 } 5598 Ok(()) 5599 } 5600 5601 fn validate_replica_policy_source_witnesses(sync_root: &Path) -> Result<(), String> { 5602 let cargo_path = sync_root.join("Cargo.toml"); 5603 let cargo_source = fs::read_to_string(&cargo_path) 5604 .map_err(|error| format!("read {}: {error}", cargo_path.display()))?; 5605 let cargo: toml::Value = toml::from_str(&cargo_source) 5606 .map_err(|error| format!("parse {}: {error}", cargo_path.display()))?; 5607 let features = cargo 5608 .get("features") 5609 .and_then(toml::Value::as_table) 5610 .ok_or_else(|| format!("replica sync {} must define features", cargo_path.display()))?; 5611 let default_features = features 5612 .get("default") 5613 .and_then(toml::Value::as_array) 5614 .ok_or_else(|| { 5615 format!( 5616 "replica sync {} must define default features", 5617 cargo_path.display() 5618 ) 5619 })?; 5620 let mut pending_default_features = default_features 5621 .iter() 5622 .filter_map(toml::Value::as_str) 5623 .collect::<Vec<_>>(); 5624 let mut visited_default_features = BTreeSet::new(); 5625 while let Some(feature) = pending_default_features.pop() { 5626 if !visited_default_features.insert(feature) { 5627 continue; 5628 } 5629 if feature == "legacy-ingest" { 5630 return Err(format!( 5631 "replica legacy-ingest must not be enabled by default features in {}", 5632 cargo_path.display() 5633 )); 5634 } 5635 if let Some(members) = features.get(feature).and_then(toml::Value::as_array) { 5636 pending_default_features.extend( 5637 members 5638 .iter() 5639 .filter_map(toml::Value::as_str) 5640 .filter(|member| features.contains_key(*member)), 5641 ); 5642 } 5643 } 5644 let legacy_features = features 5645 .get("legacy-ingest") 5646 .and_then(toml::Value::as_array) 5647 .ok_or_else(|| { 5648 format!( 5649 "replica sync {} must define the explicit legacy-ingest feature", 5650 cargo_path.display() 5651 ) 5652 })?; 5653 if !legacy_features 5654 .iter() 5655 .filter_map(toml::Value::as_str) 5656 .any(|feature| feature == "std") 5657 { 5658 return Err(format!( 5659 "replica legacy-ingest feature in {} must enable std", 5660 cargo_path.display() 5661 )); 5662 } 5663 5664 let lib_path = sync_root.join("src/lib.rs"); 5665 let lib_source = fs::read_to_string(&lib_path) 5666 .map_err(|error| format!("read {}: {error}", lib_path.display()))?; 5667 validate_replica_legacy_ingest_exports(&lib_path, &lib_source)?; 5668 5669 let types_path = sync_root.join("src/types.rs"); 5670 let types_source = fs::read_to_string(&types_path) 5671 .map_err(|error| format!("read {}: {error}", types_path.display()))?; 5672 for type_name in [ 5673 "RadrootsReplicaFarmSelector", 5674 "RadrootsReplicaSyncOptions", 5675 "RadrootsReplicaSyncRequest", 5676 ] { 5677 let witness = format!("#[serde(deny_unknown_fields)]\npub struct {type_name}"); 5678 if !types_source.contains(&witness) { 5679 return Err(format!( 5680 "replica request type {type_name} must place #[serde(deny_unknown_fields)] immediately before its public struct declaration in {}", 5681 types_path.display() 5682 )); 5683 } 5684 } 5685 if types_source.contains("include_profiles") { 5686 return Err(format!( 5687 "retired replica request identifier include_profiles is forbidden in {}", 5688 types_path.display() 5689 )); 5690 } 5691 5692 let emit_path = sync_root.join("src/emit.rs"); 5693 let emit_source = fs::read_to_string(&emit_path) 5694 .map_err(|error| format!("read {}: {error}", emit_path.display()))?; 5695 let test_module_marker = "#[cfg(test)]\nmod tests {"; 5696 let test_module_start = emit_source.rfind(test_module_marker).ok_or_else(|| { 5697 format!( 5698 "replica emit source {} must keep its bottom test module behind #[cfg(test)]", 5699 emit_path.display() 5700 ) 5701 })?; 5702 let production_source = &emit_source[..test_module_start]; 5703 if !production_source.contains("pub fn radroots_replica_sync_all_with_options(") { 5704 return Err(format!( 5705 "replica emit source {} is missing radroots_replica_sync_all_with_options", 5706 emit_path.display() 5707 )); 5708 } 5709 let production_code = production_source 5710 .lines() 5711 .filter(|line| !line.trim_start().starts_with("//")) 5712 .collect::<Vec<_>>() 5713 .join("\n"); 5714 for identifier in production_code 5715 .split(|character: char| !(character.is_ascii_alphanumeric() || character == '_')) 5716 .filter(|identifier| !identifier.is_empty()) 5717 { 5718 if identifier.to_ascii_lowercase().contains("profile") { 5719 return Err(format!( 5720 "replica emit production source {} must not contain Profile-related identifier {identifier}", 5721 emit_path.display() 5722 )); 5723 } 5724 } 5725 let compact_production = production_code 5726 .chars() 5727 .filter(|character| !character.is_ascii_whitespace()) 5728 .collect::<String>(); 5729 if compact_production.contains("kind:0") { 5730 return Err(format!( 5731 "replica emit production source {} must not construct a literal kind-0 event", 5732 emit_path.display() 5733 )); 5734 } 5735 5736 Ok(()) 5737 } 5738 5739 fn validate_replica_contract(bundle: &ContractBundle, workspace_root: &Path) -> Result<(), String> { 5740 let replica = &bundle.replica; 5741 if replica.schema_version != 1 { 5742 return Err("replica contract schema_version must be 1".to_string()); 5743 } 5744 if replica.contract.name != REPLICA_CONTRACT_NAME { 5745 return Err(format!( 5746 "replica contract name must be {REPLICA_CONTRACT_NAME}" 5747 )); 5748 } 5749 if replica.contract.version != bundle.manifest.contract.version { 5750 return Err(format!( 5751 "replica contract version {} must match manifest contract version {}", 5752 replica.contract.version, bundle.manifest.contract.version 5753 )); 5754 } 5755 if replica.contract.purpose.trim().is_empty() { 5756 return Err("replica contract purpose is required".to_string()); 5757 } 5758 5759 let manifest_family = bundle 5760 .manifest 5761 .surface 5762 .internal_replica_crates 5763 .as_ref() 5764 .ok_or_else(|| "surface.internal_replica_crates is required".to_string())?; 5765 for (field, actual, expected) in [ 5766 ( 5767 "schema", 5768 replica.crate_family.schema.as_str(), 5769 manifest_family.schema.as_str(), 5770 ), 5771 ( 5772 "storage", 5773 replica.crate_family.storage.as_str(), 5774 manifest_family.storage.as_str(), 5775 ), 5776 ( 5777 "sync", 5778 replica.crate_family.sync.as_str(), 5779 manifest_family.sync.as_str(), 5780 ), 5781 ] { 5782 validate_crate_identifier(actual, &format!("replica.crate_family.{field}"))?; 5783 if actual != expected { 5784 return Err(format!( 5785 "replica crate_family.{field} {actual} must match surface.internal_replica_crates.{field} {expected}" 5786 )); 5787 } 5788 } 5789 5790 let package_manifests = workspace_package_manifests(workspace_root)?; 5791 for (field, crate_name) in [ 5792 ("schema", replica.crate_family.schema.as_str()), 5793 ("storage", replica.crate_family.storage.as_str()), 5794 ("sync", replica.crate_family.sync.as_str()), 5795 ] { 5796 if !package_manifests.contains_key(crate_name) { 5797 return Err(format!( 5798 "replica crate_family.{field} {crate_name} must name a workspace package" 5799 )); 5800 } 5801 } 5802 5803 let manifest_policy = bundle 5804 .manifest 5805 .policy 5806 .replica 5807 .as_ref() 5808 .ok_or_else(|| "policy.replica is required".to_string())?; 5809 let policy_parity = [ 5810 ( 5811 "transport_agnostic_sync_core", 5812 replica.policy.transport_agnostic_sync_core, 5813 manifest_policy.require_transport_agnostic_sync_contract, 5814 ), 5815 ( 5816 "deterministic_emit_and_ingest", 5817 replica.policy.deterministic_emit_and_ingest, 5818 manifest_policy.require_deterministic_emit_ingest, 5819 ), 5820 ( 5821 "forbid_legacy_alias_identifiers", 5822 replica.policy.forbid_legacy_alias_identifiers, 5823 manifest_policy.forbid_legacy_alias_identifiers, 5824 ), 5825 ]; 5826 for (field, actual, expected) in policy_parity { 5827 if !actual || actual != expected { 5828 return Err(format!( 5829 "replica policy.{field} must be true and match manifest policy.replica" 5830 )); 5831 } 5832 } 5833 if replica.policy.profile_event_emission != "excluded" { 5834 return Err("replica policy.profile_event_emission must be excluded".to_string()); 5835 } 5836 if replica.policy.unknown_sync_request_fields != "reject" { 5837 return Err("replica policy.unknown_sync_request_fields must be reject".to_string()); 5838 } 5839 for (field, actual, expected) in [ 5840 ( 5841 "classified_listing_signature_verification", 5842 replica 5843 .policy 5844 .classified_listing_signature_verification 5845 .as_str(), 5846 "required_before_state", 5847 ), 5848 ( 5849 "classified_listing_head_selection", 5850 replica.policy.classified_listing_head_selection.as_str(), 5851 "raw_before_profile", 5852 ), 5853 ( 5854 "classified_listing_operational_projection", 5855 replica 5856 .policy 5857 .classified_listing_operational_projection 5858 .as_str(), 5859 "operational_partition_only", 5860 ), 5861 ( 5862 "classified_listing_excluded_or_rejected_head", 5863 replica 5864 .policy 5865 .classified_listing_excluded_or_rejected_head 5866 .as_str(), 5867 "remove_projection_and_advance", 5868 ), 5869 ( 5870 "classified_listing_head_only_ingest", 5871 replica.policy.classified_listing_head_only_ingest.as_str(), 5872 "reject_require_profile_aware", 5873 ), 5874 ( 5875 "legacy_bare_envelope_ingest", 5876 replica.policy.legacy_bare_envelope_ingest.as_str(), 5877 "explicit_non_default_feature_only", 5878 ), 5879 ( 5880 "legacy_ingest_feature", 5881 replica.policy.legacy_ingest_feature.as_str(), 5882 "legacy-ingest", 5883 ), 5884 ( 5885 "phase_1_ingest_replacement", 5886 replica.policy.phase_1_ingest_replacement.as_str(), 5887 "none", 5888 ), 5889 ( 5890 "future_product_ingest_input", 5891 replica.policy.future_product_ingest_input.as_str(), 5892 "store_produced_verified_valid_visible_admission", 5893 ), 5894 ] { 5895 if actual != expected { 5896 return Err(format!("replica policy.{field} must be {expected}")); 5897 } 5898 } 5899 5900 if replica.transfer.version != REPLICA_TRANSFER_VERSION { 5901 return Err(format!( 5902 "replica transfer.version must be {REPLICA_TRANSFER_VERSION}" 5903 )); 5904 } 5905 if replica.transfer.constant != REPLICA_TRANSFER_CONSTANT { 5906 return Err(format!( 5907 "replica transfer.constant must be {REPLICA_TRANSFER_CONSTANT}" 5908 )); 5909 } 5910 let sync_manifest = package_manifests 5911 .get(&replica.crate_family.sync) 5912 .expect("replica sync workspace package was validated"); 5913 let sync_root = sync_manifest 5914 .parent() 5915 .expect("workspace package manifest has a parent"); 5916 validate_replica_policy_source_witnesses(sync_root)?; 5917 let expected_source = sync_root 5918 .strip_prefix(workspace_root) 5919 .expect("workspace package lives under the workspace root") 5920 .join("src/types.rs"); 5921 if Path::new(&replica.transfer.source) != expected_source { 5922 return Err(format!( 5923 "replica transfer.source {} must be {}", 5924 replica.transfer.source, 5925 expected_source.display() 5926 )); 5927 } 5928 let source_path = workspace_root.join(&replica.transfer.source); 5929 let source_version = parse_replica_transfer_constant(&source_path, &replica.transfer.constant)?; 5930 if source_version != replica.transfer.version { 5931 return Err(format!( 5932 "replica transfer source constant {} value {} must match contract version {}", 5933 replica.transfer.constant, source_version, replica.transfer.version 5934 )); 5935 } 5936 5937 Ok(()) 5938 } 5939 5940 fn validate_operations_contract( 5941 bundle: &ContractBundle, 5942 operations_manifest: &OperationsContractManifest, 5943 workspace_root: &Path, 5944 ) -> Result<(), String> { 5945 validate_conformance_schema(workspace_root)?; 5946 let conformance_root = conformance_root(workspace_root); 5947 if operations_manifest.contract.name.trim().is_empty() { 5948 return Err("operations contract name is required".to_string()); 5949 } 5950 if operations_manifest.contract.version.trim().is_empty() { 5951 return Err("operations contract version is required".to_string()); 5952 } 5953 if operations_manifest.contract.source.trim().is_empty() { 5954 return Err("operations contract source is required".to_string()); 5955 } 5956 if operations_manifest.contract.name != bundle.manifest.contract.name { 5957 return Err("operations contract name must match manifest contract name".to_string()); 5958 } 5959 if operations_manifest.contract.version != bundle.manifest.contract.version { 5960 return Err("operations contract version must match manifest contract version".to_string()); 5961 } 5962 if operations_manifest.contract.source != bundle.manifest.contract.source { 5963 return Err("operations contract source must match manifest contract source".to_string()); 5964 } 5965 5966 let domains = collect_non_empty_set(&operations_manifest.public.domains, "public.domains")?; 5967 if domains.is_empty() { 5968 return Err("public.domains must not be empty".to_string()); 5969 } 5970 let shared_types = collect_non_empty_set( 5971 &operations_manifest.shared_types.public, 5972 "shared_types.public", 5973 )?; 5974 if shared_types.is_empty() { 5975 return Err("shared_types.public must not be empty".to_string()); 5976 } 5977 validate_no_retired_operation_event_names( 5978 &operations_manifest.shared_types.public, 5979 "shared_types.public", 5980 )?; 5981 let error_classes = 5982 collect_non_empty_set(&operations_manifest.errors.classes, "errors.classes")?; 5983 if error_classes.is_empty() { 5984 return Err("errors.classes must not be empty".to_string()); 5985 } 5986 if operations_manifest.operations.is_empty() { 5987 return Err("operations map must not be empty".to_string()); 5988 } 5989 5990 if let Some(provenance) = &operations_manifest.implementation_provenance { 5991 let manifest_models = collect_unique_set( 5992 &bundle.manifest.surface.model_crates, 5993 "surface.model_crates", 5994 )?; 5995 let manifest_algorithms = collect_unique_set( 5996 &bundle.manifest.surface.algorithm_crates, 5997 "surface.algorithm_crates", 5998 )?; 5999 let provenance_models = collect_unique_set( 6000 &provenance.model_crates, 6001 "implementation_provenance.model_crates", 6002 )?; 6003 let provenance_algorithms = collect_unique_set( 6004 &provenance.algorithm_crates, 6005 "implementation_provenance.algorithm_crates", 6006 )?; 6007 if provenance_models != manifest_models || provenance_algorithms != manifest_algorithms { 6008 return Err( 6009 "operations implementation_provenance must match manifest surface crates" 6010 .to_string(), 6011 ); 6012 } 6013 } 6014 6015 let mut operation_ids = BTreeSet::new(); 6016 for (operation_key, operation) in &operations_manifest.operations { 6017 if operation_key.trim().is_empty() { 6018 return Err("operations map contains an empty key".to_string()); 6019 } 6020 if operation.domain.trim().is_empty() { 6021 return Err(format!("operation {} domain is required", operation_key)); 6022 } 6023 if !domains.contains(&operation.domain) { 6024 return Err(format!( 6025 "operation {} references unknown domain {}", 6026 operation_key, operation.domain 6027 )); 6028 } 6029 if operation.id.trim().is_empty() { 6030 return Err(format!("operation {} id is required", operation_key)); 6031 } 6032 if !operation_ids.insert(operation.id.clone()) { 6033 return Err(format!("operations has duplicate id {}", operation.id)); 6034 } 6035 if operation.stability.trim().is_empty() { 6036 return Err(format!("operation {} stability is required", operation.id)); 6037 } 6038 if !operation.deterministic { 6039 return Err(format!( 6040 "operation {} deterministic must be true for the public contract", 6041 operation.id 6042 )); 6043 } 6044 if operation.inputs.is_empty() { 6045 return Err(format!( 6046 "operation {} inputs must not be empty", 6047 operation.id 6048 )); 6049 } 6050 let _ = collect_non_empty_set( 6051 &operation.inputs, 6052 &format!("operation {} inputs", operation.id), 6053 )?; 6054 validate_no_retired_operation_event_names( 6055 &operation.inputs, 6056 &format!("operation {} inputs", operation.id), 6057 )?; 6058 if operation.outputs.is_empty() { 6059 return Err(format!( 6060 "operation {} outputs must not be empty", 6061 operation.id 6062 )); 6063 } 6064 let _ = collect_non_empty_set( 6065 &operation.outputs, 6066 &format!("operation {} outputs", operation.id), 6067 )?; 6068 validate_no_retired_operation_event_names( 6069 &operation.outputs, 6070 &format!("operation {} outputs", operation.id), 6071 )?; 6072 if !error_classes.contains(&operation.error_class) { 6073 return Err(format!( 6074 "operation {} references unknown error class {}", 6075 operation.id, operation.error_class 6076 )); 6077 } 6078 if operation.signing.trim().is_empty() { 6079 return Err(format!("operation {} signing is required", operation.id)); 6080 } 6081 if operation.transport.trim().is_empty() { 6082 return Err(format!("operation {} transport is required", operation.id)); 6083 } 6084 if operation.implementation.rust_modules.is_empty() { 6085 return Err(format!( 6086 "operation {} implementation.rust_modules must not be empty", 6087 operation.id 6088 )); 6089 } 6090 let _ = collect_non_empty_set( 6091 &operation.implementation.rust_types, 6092 &format!("operation {} implementation.rust_types", operation.id), 6093 )?; 6094 validate_no_retired_operation_event_names( 6095 &operation.implementation.rust_types, 6096 &format!("operation {} implementation.rust_types", operation.id), 6097 )?; 6098 for rust_module in &operation.implementation.rust_modules { 6099 if rust_module.trim().is_empty() { 6100 return Err(format!( 6101 "operation {} implementation.rust_modules contains an empty value", 6102 operation.id 6103 )); 6104 } 6105 let path = workspace_root.join(rust_module); 6106 if !path.is_file() { 6107 return Err(format!( 6108 "operation {} references missing rust module {}", 6109 operation.id, rust_module 6110 )); 6111 } 6112 } 6113 if operation.conformance.vector.trim().is_empty() { 6114 return Err(format!( 6115 "operation {} conformance.vector is required", 6116 operation.id 6117 )); 6118 } 6119 if !operation 6120 .conformance 6121 .vector 6122 .starts_with("contracts/conformance/") 6123 { 6124 return Err(format!( 6125 "operation {} conformance.vector must live under contracts/conformance/", 6126 operation.id 6127 )); 6128 } 6129 let vector_path = workspace_root.join(&operation.conformance.vector); 6130 if !vector_path.starts_with(&conformance_root) { 6131 return Err(format!( 6132 "operation {} conformance.vector must resolve under {}", 6133 operation.id, 6134 conformance_root.display() 6135 )); 6136 } 6137 let vector = 6138 validate_conformance_vector_file(&vector_path, &operations_manifest.contract.version)?; 6139 validate_operation_case_kinds(operation, &vector)?; 6140 } 6141 6142 Ok(()) 6143 } 6144 6145 fn validate_capsule_operation_authority( 6146 operations_manifest: &OperationsContractManifest, 6147 workspace_root: &Path, 6148 ) -> Result<(), String> { 6149 let shared_types = collect_non_empty_set( 6150 &operations_manifest.shared_types.public, 6151 "shared_types.public", 6152 )?; 6153 validate_comment_operation_authority(operations_manifest, workspace_root)?; 6154 validate_deletion_operation_authority(operations_manifest, workspace_root)?; 6155 validate_admission_operation_authority(operations_manifest, workspace_root)?; 6156 validate_post_operation_authority(operations_manifest, workspace_root)?; 6157 validate_calendar_operation_authority(operations_manifest, &shared_types)?; 6158 validate_food_availability_operation_authority(operations_manifest, workspace_root) 6159 } 6160 6161 fn validate_operation_case_kinds( 6162 operation: &PublicOperationContract, 6163 vector: &ConformanceVectorFile, 6164 ) -> Result<(), String> { 6165 if operation.conformance.case_kinds.is_empty() { 6166 return Ok(()); 6167 } 6168 let case_kinds = collect_non_empty_set( 6169 &operation.conformance.case_kinds, 6170 &format!("operation {} conformance.case_kinds", operation.id), 6171 )?; 6172 if case_kinds.len() != operation.conformance.case_kinds.len() { 6173 return Err(format!( 6174 "operation {} conformance.case_kinds must not contain duplicates", 6175 operation.id 6176 )); 6177 } 6178 let prefix = format!("{}.", operation.id); 6179 let vector_kinds = vector 6180 .vectors 6181 .iter() 6182 .map(|entry| entry.kind.as_str()) 6183 .collect::<BTreeSet<_>>(); 6184 for case_kind in case_kinds { 6185 if !case_kind.starts_with(&prefix) { 6186 return Err(format!( 6187 "operation {} conformance case kind {} must start with {}", 6188 operation.id, case_kind, prefix 6189 )); 6190 } 6191 if !vector_kinds.contains(case_kind.as_str()) { 6192 return Err(format!( 6193 "operation {} conformance case kind {} is absent from {}", 6194 operation.id, case_kind, operation.conformance.vector 6195 )); 6196 } 6197 } 6198 Ok(()) 6199 } 6200 6201 fn validate_post_operation_authority( 6202 manifest: &OperationsContractManifest, 6203 workspace_root: &Path, 6204 ) -> Result<(), String> { 6205 let vector = validate_conformance_vector_file( 6206 &workspace_root.join(POST_CONFORMANCE_VECTOR_RELATIVE), 6207 &manifest.contract.version, 6208 )?; 6209 validate_post_operation_inventory(manifest, &vector) 6210 } 6211 6212 fn validate_post_operation_inventory( 6213 manifest: &OperationsContractManifest, 6214 vector: &ConformanceVectorFile, 6215 ) -> Result<(), String> { 6216 let shared_types = collect_non_empty_set( 6217 &manifest.shared_types.public, 6218 "post operation shared_types.public", 6219 )?; 6220 for required in REQUIRED_POST_PUBLIC_TYPES { 6221 if !shared_types.contains(required) { 6222 return Err(format!( 6223 "post operation authority requires shared public type {required}" 6224 )); 6225 } 6226 } 6227 6228 let expected_keys = POST_OPERATION_EXPECTATIONS 6229 .iter() 6230 .map(|expectation| expectation.key.to_string()) 6231 .collect::<BTreeSet<_>>(); 6232 let actual_keys = manifest 6233 .operations 6234 .iter() 6235 .filter(|(key, operation)| { 6236 operation.conformance.vector == POST_CONFORMANCE_VECTOR_RELATIVE 6237 || POST_OPERATION_KEY_PREFIXES 6238 .iter() 6239 .any(|prefix| key.starts_with(prefix)) 6240 || POST_OPERATION_ID_PREFIXES 6241 .iter() 6242 .any(|prefix| operation.id.starts_with(prefix)) 6243 }) 6244 .map(|(key, _)| key.clone()) 6245 .collect::<BTreeSet<_>>(); 6246 if actual_keys != expected_keys { 6247 let missing = expected_keys 6248 .difference(&actual_keys) 6249 .cloned() 6250 .collect::<BTreeSet<_>>(); 6251 let unexpected = actual_keys 6252 .difference(&expected_keys) 6253 .cloned() 6254 .collect::<BTreeSet<_>>(); 6255 return Err(format!( 6256 "post operation authority drift: missing {}; unexpected {}", 6257 join_set(&missing), 6258 join_set(&unexpected) 6259 )); 6260 } 6261 6262 let mut owners = BTreeMap::new(); 6263 for expected in POST_OPERATION_EXPECTATIONS { 6264 let operation = manifest 6265 .operations 6266 .get(expected.key) 6267 .ok_or_else(|| format!("post operation {} is required", expected.key))?; 6268 validate_post_operation_scalar(expected.key, "domain", &operation.domain, "social")?; 6269 validate_post_operation_scalar(expected.key, "id", &operation.id, expected.id)?; 6270 validate_post_operation_scalar(expected.key, "stability", &operation.stability, "beta")?; 6271 validate_post_operation_scalar( 6272 expected.key, 6273 "error_class", 6274 &operation.error_class, 6275 expected.error_class, 6276 )?; 6277 validate_post_operation_scalar( 6278 expected.key, 6279 "signing", 6280 &operation.signing, 6281 expected.signing, 6282 )?; 6283 validate_post_operation_scalar(expected.key, "transport", &operation.transport, "none")?; 6284 if !operation.deterministic { 6285 return Err(format!( 6286 "post operation {} deterministic drift: expected true, got false", 6287 expected.key 6288 )); 6289 } 6290 validate_post_operation_sequence( 6291 expected.key, 6292 "inputs", 6293 &operation.inputs, 6294 expected.inputs, 6295 )?; 6296 validate_post_operation_sequence( 6297 expected.key, 6298 "outputs", 6299 &operation.outputs, 6300 expected.outputs, 6301 )?; 6302 validate_post_operation_sequence( 6303 expected.key, 6304 "implementation.rust_modules", 6305 &operation.implementation.rust_modules, 6306 expected.rust_modules, 6307 )?; 6308 validate_post_operation_sequence( 6309 expected.key, 6310 "implementation.rust_types", 6311 &operation.implementation.rust_types, 6312 expected.rust_types, 6313 )?; 6314 validate_post_operation_scalar( 6315 expected.key, 6316 "conformance.vector", 6317 &operation.conformance.vector, 6318 POST_CONFORMANCE_VECTOR_RELATIVE, 6319 )?; 6320 validate_operation_case_kinds(operation, vector)?; 6321 if !operation 6322 .conformance 6323 .case_kinds 6324 .iter() 6325 .map(String::as_str) 6326 .eq(expected.case_kinds.iter().copied()) 6327 { 6328 return Err(format!( 6329 "post operation {} conformance.case_kinds drift: expected {:?}, got {:?}", 6330 expected.key, expected.case_kinds, operation.conformance.case_kinds 6331 )); 6332 } 6333 for case_kind in &operation.conformance.case_kinds { 6334 if let Some(previous) = owners.insert(case_kind.as_str(), expected.key) { 6335 return Err(format!( 6336 "post conformance case kind {case_kind} is multiply claimed by {previous} and {}", 6337 expected.key 6338 )); 6339 } 6340 } 6341 } 6342 6343 let mut actual_inventory = BTreeMap::new(); 6344 for entry in &vector.vectors { 6345 if actual_inventory 6346 .insert(entry.id.as_str(), entry.kind.as_str()) 6347 .is_some() 6348 { 6349 return Err(format!( 6350 "post conformance vector inventory has duplicate id {}", 6351 entry.id 6352 )); 6353 } 6354 } 6355 for kind in actual_inventory.values() { 6356 if !owners.contains_key(kind) { 6357 return Err(format!( 6358 "post conformance vector kind {kind} is not claimed by exactly one operation" 6359 )); 6360 } 6361 } 6362 let expected_inventory = POST_VECTOR_EXPECTATIONS 6363 .into_iter() 6364 .collect::<BTreeMap<_, _>>(); 6365 if actual_inventory != expected_inventory { 6366 return Err(format!( 6367 "post conformance vector inventory drift: expected {:?}, got {:?}", 6368 expected_inventory, actual_inventory 6369 )); 6370 } 6371 Ok(()) 6372 } 6373 6374 fn validate_post_operation_scalar( 6375 operation_key: &str, 6376 field: &str, 6377 actual: &str, 6378 expected: &str, 6379 ) -> Result<(), String> { 6380 if actual != expected { 6381 return Err(format!( 6382 "post operation {operation_key} {field} drift: expected {expected}, got {actual}" 6383 )); 6384 } 6385 Ok(()) 6386 } 6387 6388 fn validate_post_operation_sequence( 6389 operation_key: &str, 6390 field: &str, 6391 actual: &[String], 6392 expected: &[&str], 6393 ) -> Result<(), String> { 6394 if !actual 6395 .iter() 6396 .map(String::as_str) 6397 .eq(expected.iter().copied()) 6398 { 6399 return Err(format!( 6400 "post operation {operation_key} {field} drift: expected {:?}, got {:?}", 6401 expected, actual 6402 )); 6403 } 6404 Ok(()) 6405 } 6406 6407 fn validate_comment_operation_authority( 6408 manifest: &OperationsContractManifest, 6409 workspace_root: &Path, 6410 ) -> Result<(), String> { 6411 let vector = validate_conformance_vector_file( 6412 &workspace_root.join(COMMENT_CONFORMANCE_VECTOR_RELATIVE), 6413 &manifest.contract.version, 6414 )?; 6415 validate_comment_operation_inventory(manifest, &vector) 6416 } 6417 6418 fn validate_comment_operation_inventory( 6419 manifest: &OperationsContractManifest, 6420 vector: &ConformanceVectorFile, 6421 ) -> Result<(), String> { 6422 let shared_types = collect_non_empty_set( 6423 &manifest.shared_types.public, 6424 "comment operation shared_types.public", 6425 )?; 6426 for required in REQUIRED_COMMENT_PUBLIC_TYPES { 6427 if !shared_types.contains(required) { 6428 return Err(format!( 6429 "comment operation authority requires shared public type {required}" 6430 )); 6431 } 6432 } 6433 6434 let expected_keys = COMMENT_OPERATION_EXPECTATIONS 6435 .iter() 6436 .map(|expectation| expectation.key.to_string()) 6437 .collect::<BTreeSet<_>>(); 6438 let actual_keys = manifest 6439 .operations 6440 .iter() 6441 .filter(|(key, operation)| { 6442 operation.conformance.vector == COMMENT_CONFORMANCE_VECTOR_RELATIVE 6443 || key.starts_with("social_comment_") 6444 || operation.id.starts_with("social.comment.") 6445 }) 6446 .map(|(key, _)| key.clone()) 6447 .collect::<BTreeSet<_>>(); 6448 if actual_keys != expected_keys { 6449 let missing = expected_keys 6450 .difference(&actual_keys) 6451 .cloned() 6452 .collect::<BTreeSet<_>>(); 6453 let unexpected = actual_keys 6454 .difference(&expected_keys) 6455 .cloned() 6456 .collect::<BTreeSet<_>>(); 6457 return Err(format!( 6458 "comment operation authority drift: missing {}; unexpected {}", 6459 join_set(&missing), 6460 join_set(&unexpected) 6461 )); 6462 } 6463 6464 let mut owners = BTreeMap::new(); 6465 for expected in COMMENT_OPERATION_EXPECTATIONS { 6466 let operation = manifest 6467 .operations 6468 .get(expected.key) 6469 .ok_or_else(|| format!("comment operation {} is required", expected.key))?; 6470 validate_comment_operation_scalar(expected.key, "domain", &operation.domain, "social")?; 6471 validate_comment_operation_scalar(expected.key, "id", &operation.id, expected.id)?; 6472 validate_comment_operation_scalar(expected.key, "stability", &operation.stability, "beta")?; 6473 validate_comment_operation_scalar( 6474 expected.key, 6475 "error_class", 6476 &operation.error_class, 6477 expected.error_class, 6478 )?; 6479 validate_comment_operation_scalar( 6480 expected.key, 6481 "signing", 6482 &operation.signing, 6483 expected.signing, 6484 )?; 6485 validate_comment_operation_scalar(expected.key, "transport", &operation.transport, "none")?; 6486 if !operation.deterministic { 6487 return Err(format!( 6488 "comment operation {} deterministic drift: expected true, got false", 6489 expected.key 6490 )); 6491 } 6492 validate_comment_operation_sequence( 6493 expected.key, 6494 "inputs", 6495 &operation.inputs, 6496 expected.inputs, 6497 )?; 6498 validate_comment_operation_sequence( 6499 expected.key, 6500 "outputs", 6501 &operation.outputs, 6502 expected.outputs, 6503 )?; 6504 validate_comment_operation_sequence( 6505 expected.key, 6506 "implementation.rust_modules", 6507 &operation.implementation.rust_modules, 6508 expected.rust_modules, 6509 )?; 6510 validate_comment_operation_sequence( 6511 expected.key, 6512 "implementation.rust_types", 6513 &operation.implementation.rust_types, 6514 expected.rust_types, 6515 )?; 6516 validate_comment_operation_scalar( 6517 expected.key, 6518 "conformance.vector", 6519 &operation.conformance.vector, 6520 COMMENT_CONFORMANCE_VECTOR_RELATIVE, 6521 )?; 6522 validate_operation_case_kinds(operation, vector)?; 6523 if !operation 6524 .conformance 6525 .case_kinds 6526 .iter() 6527 .map(String::as_str) 6528 .eq(expected.case_kinds.iter().copied()) 6529 { 6530 return Err(format!( 6531 "comment operation {} conformance.case_kinds drift: expected {:?}, got {:?}", 6532 expected.key, expected.case_kinds, operation.conformance.case_kinds 6533 )); 6534 } 6535 for case_kind in &operation.conformance.case_kinds { 6536 if let Some(previous) = owners.insert(case_kind.as_str(), expected.key) { 6537 return Err(format!( 6538 "comment conformance case kind {case_kind} is multiply claimed by {previous} and {}", 6539 expected.key 6540 )); 6541 } 6542 } 6543 } 6544 6545 let expected_case_kinds = COMMENT_CASE_KINDS.into_iter().collect::<BTreeSet<_>>(); 6546 let actual_case_kinds = owners.keys().copied().collect::<BTreeSet<_>>(); 6547 if actual_case_kinds != expected_case_kinds { 6548 return Err(format!( 6549 "comment conformance case-kind authority drift: expected {:?}, got {:?}", 6550 expected_case_kinds, actual_case_kinds 6551 )); 6552 } 6553 6554 let mut actual_inventory = BTreeMap::new(); 6555 for entry in &vector.vectors { 6556 if actual_inventory 6557 .insert(entry.id.as_str(), entry.kind.as_str()) 6558 .is_some() 6559 { 6560 return Err(format!( 6561 "comment conformance vector inventory has duplicate id {}", 6562 entry.id 6563 )); 6564 } 6565 if !owners.contains_key(entry.kind.as_str()) { 6566 return Err(format!( 6567 "comment conformance vector kind {} is not claimed by exactly one operation", 6568 entry.kind 6569 )); 6570 } 6571 } 6572 let expected_inventory = COMMENT_VECTOR_EXPECTATIONS 6573 .into_iter() 6574 .collect::<BTreeMap<_, _>>(); 6575 if actual_inventory != expected_inventory { 6576 return Err(format!( 6577 "comment conformance vector inventory drift: expected {:?}, got {:?}", 6578 expected_inventory, actual_inventory 6579 )); 6580 } 6581 6582 Ok(()) 6583 } 6584 6585 fn validate_comment_operation_scalar( 6586 operation_key: &str, 6587 field: &str, 6588 actual: &str, 6589 expected: &str, 6590 ) -> Result<(), String> { 6591 if actual != expected { 6592 return Err(format!( 6593 "comment operation {operation_key} {field} drift: expected {expected}, got {actual}" 6594 )); 6595 } 6596 Ok(()) 6597 } 6598 6599 fn validate_comment_operation_sequence( 6600 operation_key: &str, 6601 field: &str, 6602 actual: &[String], 6603 expected: &[&str], 6604 ) -> Result<(), String> { 6605 if !actual 6606 .iter() 6607 .map(String::as_str) 6608 .eq(expected.iter().copied()) 6609 { 6610 return Err(format!( 6611 "comment operation {operation_key} {field} drift: expected {:?}, got {:?}", 6612 expected, actual 6613 )); 6614 } 6615 Ok(()) 6616 } 6617 6618 fn validate_deletion_operation_authority( 6619 manifest: &OperationsContractManifest, 6620 workspace_root: &Path, 6621 ) -> Result<(), String> { 6622 let request_vector = validate_conformance_vector_file( 6623 &workspace_root.join(DELETION_CONFORMANCE_VECTOR_RELATIVE), 6624 &manifest.contract.version, 6625 )?; 6626 let suppression_vector = validate_conformance_vector_file( 6627 &workspace_root.join(DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE), 6628 &manifest.contract.version, 6629 )?; 6630 validate_deletion_operation_inventory(manifest, &request_vector, &suppression_vector) 6631 } 6632 6633 fn validate_deletion_operation_inventory( 6634 manifest: &OperationsContractManifest, 6635 request_vector: &ConformanceVectorFile, 6636 suppression_vector: &ConformanceVectorFile, 6637 ) -> Result<(), String> { 6638 let shared_types = collect_non_empty_set( 6639 &manifest.shared_types.public, 6640 "deletion operation shared_types.public", 6641 )?; 6642 let expected_public_types = REQUIRED_DELETION_PUBLIC_TYPES 6643 .into_iter() 6644 .collect::<BTreeSet<_>>(); 6645 let actual_public_types = shared_types 6646 .iter() 6647 .map(String::as_str) 6648 .filter(|name| { 6649 name.contains("Nip09Deletion") 6650 || name.starts_with("RadrootsNip09") 6651 || name.starts_with("Nip01Coordinate") 6652 || matches!( 6653 *name, 6654 "Nip01EventWireParts" | "EventEnvelope" | "RadrootsSignatureVerifiedEvent" 6655 ) 6656 }) 6657 .collect::<BTreeSet<_>>(); 6658 if actual_public_types != expected_public_types { 6659 return Err(format!( 6660 "deletion operation public-type authority drift: expected {:?}, got {:?}", 6661 expected_public_types, actual_public_types 6662 )); 6663 } 6664 6665 let expected_keys = DELETION_OPERATION_EXPECTATIONS 6666 .iter() 6667 .map(|expectation| expectation.key.to_string()) 6668 .collect::<BTreeSet<_>>(); 6669 let actual_keys = manifest 6670 .operations 6671 .iter() 6672 .filter(|(key, operation)| { 6673 operation.conformance.vector == DELETION_CONFORMANCE_VECTOR_RELATIVE 6674 || operation.conformance.vector == DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE 6675 || key.starts_with("social_deletion_request_") 6676 || operation.id.starts_with("social.deletion_request.") 6677 }) 6678 .map(|(key, _)| key.clone()) 6679 .collect::<BTreeSet<_>>(); 6680 if actual_keys != expected_keys { 6681 let missing = expected_keys 6682 .difference(&actual_keys) 6683 .cloned() 6684 .collect::<BTreeSet<_>>(); 6685 let unexpected = actual_keys 6686 .difference(&expected_keys) 6687 .cloned() 6688 .collect::<BTreeSet<_>>(); 6689 return Err(format!( 6690 "deletion operation authority drift: missing {}; unexpected {}", 6691 join_set(&missing), 6692 join_set(&unexpected) 6693 )); 6694 } 6695 6696 let mut owners = BTreeMap::new(); 6697 for expected in DELETION_OPERATION_EXPECTATIONS { 6698 let vector = match expected.vector { 6699 DELETION_CONFORMANCE_VECTOR_RELATIVE => request_vector, 6700 DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE => suppression_vector, 6701 unexpected => { 6702 return Err(format!( 6703 "deletion operation authority contains unsupported vector {unexpected}" 6704 )); 6705 } 6706 }; 6707 let operation = manifest 6708 .operations 6709 .get(expected.key) 6710 .ok_or_else(|| format!("deletion operation {} is required", expected.key))?; 6711 validate_deletion_operation_scalar(expected.key, "domain", &operation.domain, "social")?; 6712 validate_deletion_operation_scalar(expected.key, "id", &operation.id, expected.id)?; 6713 validate_deletion_operation_scalar( 6714 expected.key, 6715 "stability", 6716 &operation.stability, 6717 "beta", 6718 )?; 6719 validate_deletion_operation_scalar( 6720 expected.key, 6721 "error_class", 6722 &operation.error_class, 6723 expected.error_class, 6724 )?; 6725 validate_deletion_operation_scalar( 6726 expected.key, 6727 "signing", 6728 &operation.signing, 6729 expected.signing, 6730 )?; 6731 validate_deletion_operation_scalar( 6732 expected.key, 6733 "transport", 6734 &operation.transport, 6735 "none", 6736 )?; 6737 if !operation.deterministic { 6738 return Err(format!( 6739 "deletion operation {} deterministic drift: expected true, got false", 6740 expected.key 6741 )); 6742 } 6743 validate_deletion_operation_sequence( 6744 expected.key, 6745 "inputs", 6746 &operation.inputs, 6747 expected.inputs, 6748 )?; 6749 validate_deletion_operation_sequence( 6750 expected.key, 6751 "outputs", 6752 &operation.outputs, 6753 expected.outputs, 6754 )?; 6755 validate_deletion_operation_sequence( 6756 expected.key, 6757 "implementation.rust_modules", 6758 &operation.implementation.rust_modules, 6759 expected.rust_modules, 6760 )?; 6761 validate_deletion_operation_sequence( 6762 expected.key, 6763 "implementation.rust_types", 6764 &operation.implementation.rust_types, 6765 expected.rust_types, 6766 )?; 6767 validate_deletion_operation_scalar( 6768 expected.key, 6769 "conformance.vector", 6770 &operation.conformance.vector, 6771 expected.vector, 6772 )?; 6773 validate_operation_case_kinds(operation, vector)?; 6774 if !operation 6775 .conformance 6776 .case_kinds 6777 .iter() 6778 .map(String::as_str) 6779 .eq(expected.case_kinds.iter().copied()) 6780 { 6781 return Err(format!( 6782 "deletion operation {} conformance.case_kinds drift: expected {:?}, got {:?}", 6783 expected.key, expected.case_kinds, operation.conformance.case_kinds 6784 )); 6785 } 6786 for case_kind in &operation.conformance.case_kinds { 6787 if let Some(previous) = owners.insert(case_kind.as_str(), expected.key) { 6788 return Err(format!( 6789 "deletion conformance case kind {case_kind} is multiply claimed by {previous} and {}", 6790 expected.key 6791 )); 6792 } 6793 } 6794 } 6795 6796 let expected_case_kinds = DELETION_CASE_KINDS.into_iter().collect::<BTreeSet<_>>(); 6797 let actual_case_kinds = owners.keys().copied().collect::<BTreeSet<_>>(); 6798 if actual_case_kinds != expected_case_kinds { 6799 return Err(format!( 6800 "deletion conformance case-kind authority drift: expected {:?}, got {:?}", 6801 expected_case_kinds, actual_case_kinds 6802 )); 6803 } 6804 6805 let mut actual_inventory = BTreeMap::new(); 6806 for entry in &request_vector.vectors { 6807 validate_deletion_vector_shape(entry)?; 6808 if actual_inventory 6809 .insert(entry.id.as_str(), entry.kind.as_str()) 6810 .is_some() 6811 { 6812 return Err(format!( 6813 "deletion conformance vector inventory has duplicate id {}", 6814 entry.id 6815 )); 6816 } 6817 if !entry.id.starts_with("nip09_") { 6818 return Err(format!( 6819 "deletion conformance vector id {} must use the nip09_ prefix", 6820 entry.id 6821 )); 6822 } 6823 if !owners.contains_key(entry.kind.as_str()) { 6824 return Err(format!( 6825 "deletion conformance vector kind {} is not claimed by exactly one operation", 6826 entry.kind 6827 )); 6828 } 6829 } 6830 let mut expected_inventory = BTreeMap::new(); 6831 for (ids, kind) in [ 6832 ( 6833 DELETION_AUTHORED_VALID_IDS.as_slice(), 6834 "social.deletion_request.build_authored_draft.valid", 6835 ), 6836 ( 6837 DELETION_AUTHORED_INVALID_IDS.as_slice(), 6838 "social.deletion_request.build_authored_draft.invalid", 6839 ), 6840 ( 6841 DELETION_PROJECT_VALID_IDS.as_slice(), 6842 "social.deletion_request.project_verified_event.valid", 6843 ), 6844 ( 6845 DELETION_PROJECT_INVALID_IDS.as_slice(), 6846 "social.deletion_request.project_verified_event.invalid", 6847 ), 6848 ( 6849 DELETION_ADMIT_VALID_IDS.as_slice(), 6850 "social.deletion_request.verify_and_admit_event.valid", 6851 ), 6852 ( 6853 DELETION_ADMIT_INVALID_IDS.as_slice(), 6854 "social.deletion_request.verify_and_admit_event.invalid", 6855 ), 6856 ] { 6857 for id in ids { 6858 if expected_inventory.insert(*id, kind).is_some() { 6859 return Err(format!( 6860 "deletion authority contains duplicate expected vector id {id}" 6861 )); 6862 } 6863 } 6864 } 6865 if actual_inventory != expected_inventory { 6866 return Err(format!( 6867 "deletion conformance vector inventory drift: expected {:?}, got {:?}", 6868 expected_inventory, actual_inventory 6869 )); 6870 } 6871 6872 validate_deletion_suppression_vector_inventory(suppression_vector, &owners) 6873 } 6874 6875 fn validate_deletion_suppression_vector_inventory( 6876 vector: &ConformanceVectorFile, 6877 owners: &BTreeMap<&str, &str>, 6878 ) -> Result<(), String> { 6879 if vector.suite != "nip09_suppression_evaluator" { 6880 return Err(format!( 6881 "deletion suppression conformance suite drift: expected nip09_suppression_evaluator, got {}", 6882 vector.suite 6883 )); 6884 } 6885 6886 let expected_kind = "social.deletion_request.evaluate_suppression.valid"; 6887 if owners.get(expected_kind).copied() != Some("social_deletion_request_evaluate_suppression") { 6888 return Err(format!( 6889 "deletion suppression conformance kind {expected_kind} is not owned by the evaluator operation" 6890 )); 6891 } 6892 6893 let expected_inventory = DELETION_SUPPRESSION_VALID_IDS 6894 .into_iter() 6895 .map(|id| (id, expected_kind)) 6896 .collect::<BTreeMap<_, _>>(); 6897 let mut actual_inventory = BTreeMap::new(); 6898 for entry in &vector.vectors { 6899 validate_deletion_suppression_vector_shape(entry)?; 6900 if actual_inventory 6901 .insert(entry.id.as_str(), entry.kind.as_str()) 6902 .is_some() 6903 { 6904 return Err(format!( 6905 "deletion suppression conformance vector inventory has duplicate id {}", 6906 entry.id 6907 )); 6908 } 6909 if !entry.id.starts_with("nip09_suppress_") { 6910 return Err(format!( 6911 "deletion suppression conformance vector id {} must use the nip09_suppress_ prefix", 6912 entry.id 6913 )); 6914 } 6915 if entry.kind != expected_kind { 6916 return Err(format!( 6917 "deletion suppression conformance vector {} kind drift: expected {expected_kind}, got {}", 6918 entry.id, entry.kind 6919 )); 6920 } 6921 } 6922 if actual_inventory != expected_inventory { 6923 return Err(format!( 6924 "deletion suppression conformance vector inventory drift: expected {:?}, got {:?}", 6925 expected_inventory, actual_inventory 6926 )); 6927 } 6928 6929 Ok(()) 6930 } 6931 6932 fn validate_deletion_suppression_vector_shape( 6933 entry: &ConformanceVectorEntry, 6934 ) -> Result<(), String> { 6935 validate_deletion_suppression_forbidden_material(&entry.input, &format!("{}.input", entry.id))?; 6936 let expected_value = entry.expected_value()?; 6937 validate_deletion_suppression_forbidden_material( 6938 expected_value, 6939 &format!("{}.expected", entry.id), 6940 )?; 6941 6942 let input = deletion_object(&entry.input, &format!("{} input", entry.id))?; 6943 validate_deletion_object_keys( 6944 input, 6945 &format!("{} input", entry.id), 6946 &["request_event_jsons", "target_event_json"], 6947 )?; 6948 let target_event_json = input 6949 .get("target_event_json") 6950 .and_then(Value::as_str) 6951 .ok_or_else(|| { 6952 format!( 6953 "deletion suppression vector {} input.target_event_json must be a string", 6954 entry.id 6955 ) 6956 })?; 6957 validate_deletion_suppression_fixed_event( 6958 target_event_json, 6959 &format!("{} target_event_json", entry.id), 6960 None, 6961 )?; 6962 6963 let request_event_jsons = input 6964 .get("request_event_jsons") 6965 .and_then(Value::as_array) 6966 .ok_or_else(|| { 6967 format!( 6968 "deletion suppression vector {} input.request_event_jsons must be an array", 6969 entry.id 6970 ) 6971 })?; 6972 let mut request_ids = BTreeSet::new(); 6973 for (index, value) in request_event_jsons.iter().enumerate() { 6974 let event_json = value.as_str().ok_or_else(|| { 6975 format!( 6976 "deletion suppression vector {} input.request_event_jsons[{index}] must be a string", 6977 entry.id 6978 ) 6979 })?; 6980 let event = validate_deletion_suppression_fixed_event( 6981 event_json, 6982 &format!("{} request_event_jsons[{index}]", entry.id), 6983 Some(5), 6984 )?; 6985 request_ids.insert(event.id); 6986 } 6987 6988 let expected = deletion_object(expected_value, &format!("{} expected", entry.id))?; 6989 validate_deletion_object_keys( 6990 expected, 6991 &format!("{} expected", entry.id), 6992 &["address_reference", "event_reference", "outcome", "reason"], 6993 )?; 6994 let outcome = expected 6995 .get("outcome") 6996 .and_then(Value::as_str) 6997 .ok_or_else(|| { 6998 format!( 6999 "deletion suppression vector {} expected.outcome must be a string", 7000 entry.id 7001 ) 7002 })?; 7003 if !matches!(outcome, "visible" | "suppressed") { 7004 return Err(format!( 7005 "deletion suppression vector {} expected.outcome is unsupported: {outcome}", 7006 entry.id 7007 )); 7008 } 7009 let reason = expected 7010 .get("reason") 7011 .and_then(Value::as_str) 7012 .ok_or_else(|| { 7013 format!( 7014 "deletion suppression vector {} expected.reason must be a string", 7015 entry.id 7016 ) 7017 })?; 7018 if !matches!( 7019 reason, 7020 "deletion_request_immune" 7021 | "deletion_no_authorized_reference" 7022 | "deletion_request_author_mismatch" 7023 | "deletion_address_cutoff_precedes_target" 7024 | "deletion_event_id_reference" 7025 | "deletion_address_reference" 7026 | "deletion_event_id_and_address_reference" 7027 ) { 7028 return Err(format!( 7029 "deletion suppression vector {} expected.reason is unsupported: {reason}", 7030 entry.id 7031 )); 7032 } 7033 7034 let event_reference = validate_deletion_suppression_event_reference( 7035 expected 7036 .get("event_reference") 7037 .expect("exact expected keys contain event_reference"), 7038 &entry.id, 7039 &request_ids, 7040 )?; 7041 let address_reference = validate_deletion_suppression_address_reference( 7042 expected 7043 .get("address_reference") 7044 .expect("exact expected keys contain address_reference"), 7045 &entry.id, 7046 &request_ids, 7047 )?; 7048 let decision_shape_matches = match reason { 7049 "deletion_request_immune" 7050 | "deletion_no_authorized_reference" 7051 | "deletion_request_author_mismatch" => { 7052 outcome == "visible" && !event_reference && !address_reference 7053 } 7054 "deletion_address_cutoff_precedes_target" => { 7055 outcome == "visible" && !event_reference && address_reference 7056 } 7057 "deletion_address_reference" => { 7058 outcome == "suppressed" && !event_reference && address_reference 7059 } 7060 "deletion_event_id_and_address_reference" => { 7061 outcome == "suppressed" && event_reference && address_reference 7062 } 7063 "deletion_event_id_reference" => outcome == "suppressed" && event_reference, 7064 _ => unreachable!("supported reason matched above"), 7065 }; 7066 if !decision_shape_matches { 7067 return Err(format!( 7068 "deletion suppression vector {} expected decision shape is inconsistent with reason {reason}", 7069 entry.id 7070 )); 7071 } 7072 7073 Ok(()) 7074 } 7075 7076 fn validate_deletion_suppression_fixed_event( 7077 event_json: &str, 7078 label: &str, 7079 expected_kind: Option<u32>, 7080 ) -> Result<DeletionConformanceRawEvent, String> { 7081 if contains_nsec_material(event_json) { 7082 return Err(format!( 7083 "deletion suppression vector {label} contains forbidden nsec material" 7084 )); 7085 } 7086 if contains_approved_fixture_secret(event_json) { 7087 return Err(format!( 7088 "deletion suppression vector {label} contains forbidden approved fixture secret material" 7089 )); 7090 } 7091 let raw = serde_json::from_str::<DeletionConformanceRawEvent>(event_json).map_err(|error| { 7092 format!("deletion suppression vector {label} has invalid fixed event shape: {error}") 7093 })?; 7094 if expected_kind.is_some_and(|kind| raw.kind != kind) { 7095 return Err(format!( 7096 "deletion suppression vector {label} must contain a kind-5 deletion request" 7097 )); 7098 } 7099 let canonical = serde_json::to_string(&raw).map_err(|error| { 7100 format!("deletion suppression vector {label} cannot be reserialized: {error}") 7101 })?; 7102 if canonical != event_json { 7103 return Err(format!( 7104 "deletion suppression vector {label} must be compact canonical JSON" 7105 )); 7106 } 7107 Ok(raw) 7108 } 7109 7110 fn validate_deletion_suppression_event_reference( 7111 value: &Value, 7112 id: &str, 7113 request_ids: &BTreeSet<String>, 7114 ) -> Result<bool, String> { 7115 if value.is_null() { 7116 return Ok(false); 7117 } 7118 let reference = deletion_object(value, &format!("{id} expected.event_reference"))?; 7119 validate_deletion_object_keys( 7120 reference, 7121 &format!("{id} expected.event_reference"), 7122 &["request_id"], 7123 )?; 7124 validate_deletion_suppression_request_id( 7125 reference.get("request_id"), 7126 id, 7127 "event_reference.request_id", 7128 request_ids, 7129 )?; 7130 Ok(true) 7131 } 7132 7133 fn validate_deletion_suppression_address_reference( 7134 value: &Value, 7135 id: &str, 7136 request_ids: &BTreeSet<String>, 7137 ) -> Result<bool, String> { 7138 if value.is_null() { 7139 return Ok(false); 7140 } 7141 let reference = deletion_object(value, &format!("{id} expected.address_reference"))?; 7142 validate_deletion_object_keys( 7143 reference, 7144 &format!("{id} expected.address_reference"), 7145 &["coordinate", "inclusive_cutoff", "request_id"], 7146 )?; 7147 let coordinate = reference 7148 .get("coordinate") 7149 .and_then(Value::as_str) 7150 .ok_or_else(|| { 7151 format!( 7152 "deletion suppression vector {id} expected.address_reference.coordinate must be a string" 7153 ) 7154 })?; 7155 validate_deletion_suppression_coordinate(coordinate, id)?; 7156 if !reference 7157 .get("inclusive_cutoff") 7158 .is_some_and(|value| value.as_u64().is_some()) 7159 { 7160 return Err(format!( 7161 "deletion suppression vector {id} expected.address_reference.inclusive_cutoff must be an unsigned integer" 7162 )); 7163 } 7164 validate_deletion_suppression_request_id( 7165 reference.get("request_id"), 7166 id, 7167 "address_reference.request_id", 7168 request_ids, 7169 )?; 7170 Ok(true) 7171 } 7172 7173 fn validate_deletion_suppression_request_id( 7174 value: Option<&Value>, 7175 id: &str, 7176 field: &str, 7177 request_ids: &BTreeSet<String>, 7178 ) -> Result<(), String> { 7179 let request_id = value.and_then(Value::as_str).ok_or_else(|| { 7180 format!("deletion suppression vector {id} expected.{field} must be a string") 7181 })?; 7182 if request_id.len() != 64 7183 || !request_id 7184 .bytes() 7185 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) 7186 { 7187 return Err(format!( 7188 "deletion suppression vector {id} expected.{field} must be lowercase 64-character hex" 7189 )); 7190 } 7191 if !request_ids.contains(request_id) { 7192 return Err(format!( 7193 "deletion suppression vector {id} expected.{field} must identify an input request" 7194 )); 7195 } 7196 Ok(()) 7197 } 7198 7199 fn validate_deletion_suppression_coordinate(coordinate: &str, id: &str) -> Result<(), String> { 7200 let mut parts = coordinate.splitn(3, ':'); 7201 let kind_text = parts.next().unwrap_or_default(); 7202 let pubkey = parts.next().unwrap_or_default(); 7203 let identifier = parts.next().ok_or_else(|| { 7204 format!( 7205 "deletion suppression vector {id} expected.address_reference.coordinate has invalid format" 7206 ) 7207 })?; 7208 let kind = kind_text.parse::<u32>().map_err(|_| { 7209 format!( 7210 "deletion suppression vector {id} expected.address_reference.coordinate kind is invalid" 7211 ) 7212 })?; 7213 if kind.to_string() != kind_text 7214 || pubkey.len() != 64 7215 || !pubkey 7216 .bytes() 7217 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte)) 7218 || (!matches!(kind, 0 | 3) 7219 && !(10_000..=19_999).contains(&kind) 7220 && !(30_000..=39_999).contains(&kind)) 7221 || ((matches!(kind, 0 | 3) || (10_000..=19_999).contains(&kind)) && !identifier.is_empty()) 7222 { 7223 return Err(format!( 7224 "deletion suppression vector {id} expected.address_reference.coordinate is not canonical" 7225 )); 7226 } 7227 Ok(()) 7228 } 7229 7230 fn validate_deletion_suppression_forbidden_material( 7231 value: &Value, 7232 path: &str, 7233 ) -> Result<(), String> { 7234 match value { 7235 Value::Object(object) => { 7236 for (key, child) in object { 7237 let normalized = key.to_ascii_lowercase(); 7238 if matches!(normalized.as_str(), "base" | "mutation") 7239 || normalized.contains("seed") 7240 || normalized.contains("generator") 7241 || normalized.contains("recipe") 7242 || normalized.contains("secret_key") 7243 || normalized.contains("private_key") 7244 || normalized.contains("signing_key") 7245 || normalized.contains("boundary") 7246 { 7247 return Err(format!( 7248 "deletion suppression vector contains forbidden metadata key {path}.{key}" 7249 )); 7250 } 7251 validate_deletion_suppression_forbidden_material(child, &format!("{path}.{key}"))?; 7252 } 7253 } 7254 Value::Array(values) => { 7255 for (index, child) in values.iter().enumerate() { 7256 validate_deletion_suppression_forbidden_material( 7257 child, 7258 &format!("{path}[{index}]"), 7259 )?; 7260 } 7261 } 7262 Value::String(string) => { 7263 if contains_nsec_material(string) { 7264 return Err(format!( 7265 "deletion suppression vector contains forbidden nsec material at {path}" 7266 )); 7267 } 7268 if contains_approved_fixture_secret(string) { 7269 return Err(format!( 7270 "deletion suppression vector contains forbidden approved fixture secret material at {path}" 7271 )); 7272 } 7273 } 7274 _ => {} 7275 } 7276 Ok(()) 7277 } 7278 7279 fn validate_deletion_vector_shape(entry: &ConformanceVectorEntry) -> Result<(), String> { 7280 validate_deletion_forbidden_metadata(&entry.input, &format!("{}.input", entry.id))?; 7281 let expected_value = entry.expected_value()?; 7282 validate_deletion_forbidden_metadata(expected_value, &format!("{}.expected", entry.id))?; 7283 7284 let input = deletion_object(&entry.input, &format!("{} input", entry.id))?; 7285 let is_authored = entry 7286 .kind 7287 .starts_with("social.deletion_request.build_authored_draft."); 7288 let is_valid = entry.kind.ends_with(".valid"); 7289 if is_authored { 7290 validate_deletion_object_keys( 7291 input, 7292 &format!("{} input", entry.id), 7293 &["address_targets", "content", "event_targets"], 7294 )?; 7295 if !input.get("content").is_some_and(Value::is_string) { 7296 return Err(format!( 7297 "deletion vector {} input.content must be a string", 7298 entry.id 7299 )); 7300 } 7301 let event_targets = input 7302 .get("event_targets") 7303 .and_then(Value::as_array) 7304 .ok_or_else(|| { 7305 format!( 7306 "deletion vector {} input.event_targets must be an array", 7307 entry.id 7308 ) 7309 })?; 7310 for (index, target) in event_targets.iter().enumerate() { 7311 let target = deletion_object( 7312 target, 7313 &format!("{} input.event_targets[{index}]", entry.id), 7314 )?; 7315 validate_deletion_object_keys( 7316 target, 7317 &format!("{} input.event_targets[{index}]", entry.id), 7318 &["event_id", "kind"], 7319 )?; 7320 if !target.get("event_id").is_some_and(Value::is_string) 7321 || !target.get("kind").is_some_and(Value::is_u64) 7322 { 7323 return Err(format!( 7324 "deletion vector {} input.event_targets[{index}] must contain string event_id and unsigned kind", 7325 entry.id 7326 )); 7327 } 7328 } 7329 let address_targets = input 7330 .get("address_targets") 7331 .and_then(Value::as_array) 7332 .ok_or_else(|| { 7333 format!( 7334 "deletion vector {} input.address_targets must be an array", 7335 entry.id 7336 ) 7337 })?; 7338 if !address_targets.iter().all(Value::is_string) { 7339 return Err(format!( 7340 "deletion vector {} input.address_targets must contain only strings", 7341 entry.id 7342 )); 7343 } 7344 } else { 7345 validate_deletion_object_keys(input, &format!("{} input", entry.id), &["event_json"])?; 7346 let event_json = input 7347 .get("event_json") 7348 .and_then(Value::as_str) 7349 .ok_or_else(|| { 7350 format!( 7351 "deletion vector {} input.event_json must be a string", 7352 entry.id 7353 ) 7354 })?; 7355 if contains_nsec_material(event_json) { 7356 return Err(format!( 7357 "deletion vector {} input.event_json contains forbidden nsec material", 7358 entry.id 7359 )); 7360 } 7361 let raw = 7362 serde_json::from_str::<DeletionConformanceRawEvent>(event_json).map_err(|error| { 7363 format!( 7364 "deletion vector {} input.event_json has invalid fixed event shape: {error}", 7365 entry.id 7366 ) 7367 })?; 7368 let canonical = serde_json::to_string(&raw).map_err(|error| { 7369 format!( 7370 "deletion vector {} input.event_json cannot be reserialized: {error}", 7371 entry.id 7372 ) 7373 })?; 7374 if canonical != event_json { 7375 return Err(format!( 7376 "deletion vector {} input.event_json must be compact canonical JSON", 7377 entry.id 7378 )); 7379 } 7380 } 7381 7382 let expected = deletion_object(expected_value, &format!("{} expected", entry.id))?; 7383 if !is_valid { 7384 validate_deletion_object_keys(expected, &format!("{} expected", entry.id), &["error"])?; 7385 if !expected 7386 .get("error") 7387 .and_then(Value::as_str) 7388 .is_some_and(|error| !error.is_empty()) 7389 { 7390 return Err(format!( 7391 "deletion vector {} expected.error must be a non-empty string", 7392 entry.id 7393 )); 7394 } 7395 } else if is_authored { 7396 validate_deletion_object_keys( 7397 expected, 7398 &format!("{} expected", entry.id), 7399 &["content", "kind", "tags"], 7400 )?; 7401 if expected.get("kind").and_then(Value::as_u64) != Some(5) 7402 || !expected.get("content").is_some_and(Value::is_string) 7403 || !expected.get("tags").is_some_and(Value::is_array) 7404 { 7405 return Err(format!( 7406 "deletion vector {} authored expected output must contain kind 5, string content, and tags", 7407 entry.id 7408 )); 7409 } 7410 } else { 7411 validate_deletion_object_keys( 7412 expected, 7413 &format!("{} expected", entry.id), 7414 &[ 7415 "address_targets", 7416 "contract_id", 7417 "diagnostics", 7418 "event_targets", 7419 "kind_advisories", 7420 "raw_tags", 7421 ], 7422 )?; 7423 if expected.get("contract_id").and_then(Value::as_str) 7424 != Some("radroots.social.deletion_request.v1") 7425 { 7426 return Err(format!( 7427 "deletion vector {} expected.contract_id drifted", 7428 entry.id 7429 )); 7430 } 7431 for field in [ 7432 "address_targets", 7433 "diagnostics", 7434 "event_targets", 7435 "kind_advisories", 7436 "raw_tags", 7437 ] { 7438 if !expected.get(field).is_some_and(Value::is_array) { 7439 return Err(format!( 7440 "deletion vector {} expected.{field} must be an array", 7441 entry.id 7442 )); 7443 } 7444 } 7445 } 7446 7447 Ok(()) 7448 } 7449 7450 const APPROVED_FIXTURE_SECRET_TEXT_SHA256: [&str; 4] = [ 7451 "abd5b64bb0a9a0b9b2e928edb278d0f4d442d16e620ac56570c354a040f4e01a", 7452 "619b2fc89e98c17205800071802b3f06e12b05b79401da800b8b13aa8597d240", 7453 "82e759d54455fbfa5b9c58367b37f1fbc3d54becc097dcdf71fa48ee0af6b2a6", 7454 "510b06b0d391a517860b8e406cdc827b0481f2e449c9817b73adad07f4ff02a7", 7455 ]; 7456 7457 fn validate_deletion_forbidden_metadata(value: &Value, path: &str) -> Result<(), String> { 7458 match value { 7459 Value::Object(object) => { 7460 for (key, child) in object { 7461 if is_deletion_forbidden_metadata_key(key) { 7462 return Err(format!( 7463 "deletion vector contains forbidden metadata key {path}.{key}" 7464 )); 7465 } 7466 validate_deletion_forbidden_metadata(child, &format!("{path}.{key}"))?; 7467 } 7468 } 7469 Value::Array(values) => { 7470 for (index, child) in values.iter().enumerate() { 7471 validate_deletion_forbidden_metadata(child, &format!("{path}[{index}]"))?; 7472 } 7473 } 7474 Value::String(string) => { 7475 if contains_nsec_material(string) { 7476 return Err(format!( 7477 "deletion vector contains forbidden nsec material at {path}" 7478 )); 7479 } 7480 if contains_approved_fixture_secret(string) { 7481 return Err(format!( 7482 "deletion vector contains forbidden approved fixture secret material at {path}" 7483 )); 7484 } 7485 } 7486 _ => {} 7487 } 7488 Ok(()) 7489 } 7490 7491 fn is_deletion_forbidden_metadata_key(key: &str) -> bool { 7492 let normalized = key.to_ascii_lowercase(); 7493 matches!(normalized.as_str(), "base" | "mutation") 7494 || normalized.contains("seed") 7495 || normalized.contains("generator") 7496 || normalized.contains("recipe") 7497 || normalized.contains("secret_key") 7498 || normalized.contains("private_key") 7499 || normalized.contains("signing_key") 7500 || normalized.contains("boundary") 7501 || normalized.contains("authorization") 7502 || normalized.contains("authorized") 7503 || normalized.contains("cutoff") 7504 || normalized.contains("evaluator") 7505 || normalized.contains("store_mutation") 7506 || normalized.contains("suppression") 7507 || normalized.contains("suppressed") 7508 || normalized == "effect" 7509 || normalized.ends_with("_effect") 7510 || normalized == "effects" 7511 } 7512 7513 fn contains_nsec_material(value: &str) -> bool { 7514 value.to_ascii_lowercase().contains("nsec1") 7515 } 7516 7517 fn contains_approved_fixture_secret(value: &str) -> bool { 7518 value 7519 .to_ascii_lowercase() 7520 .as_bytes() 7521 .windows(64) 7522 .filter(|window| window.iter().all(u8::is_ascii_hexdigit)) 7523 .any(|window| { 7524 let digest = hex::encode(Sha256::digest(window)); 7525 APPROVED_FIXTURE_SECRET_TEXT_SHA256.contains(&digest.as_str()) 7526 }) 7527 } 7528 7529 fn deletion_object<'a>( 7530 value: &'a Value, 7531 label: &str, 7532 ) -> Result<&'a serde_json::Map<String, Value>, String> { 7533 value 7534 .as_object() 7535 .ok_or_else(|| format!("deletion vector {label} must be an object")) 7536 } 7537 7538 fn validate_deletion_object_keys( 7539 object: &serde_json::Map<String, Value>, 7540 label: &str, 7541 expected: &[&str], 7542 ) -> Result<(), String> { 7543 let actual = object.keys().map(String::as_str).collect::<BTreeSet<_>>(); 7544 let expected = expected.iter().copied().collect::<BTreeSet<_>>(); 7545 if actual != expected { 7546 return Err(format!( 7547 "deletion vector {label} keys drift: expected {:?}, got {:?}", 7548 expected, actual 7549 )); 7550 } 7551 Ok(()) 7552 } 7553 7554 fn validate_deletion_operation_scalar( 7555 operation_key: &str, 7556 field: &str, 7557 actual: &str, 7558 expected: &str, 7559 ) -> Result<(), String> { 7560 if actual != expected { 7561 return Err(format!( 7562 "deletion operation {operation_key} {field} drift: expected {expected}, got {actual}" 7563 )); 7564 } 7565 Ok(()) 7566 } 7567 7568 fn validate_deletion_operation_sequence( 7569 operation_key: &str, 7570 field: &str, 7571 actual: &[String], 7572 expected: &[&str], 7573 ) -> Result<(), String> { 7574 if !actual 7575 .iter() 7576 .map(String::as_str) 7577 .eq(expected.iter().copied()) 7578 { 7579 return Err(format!( 7580 "deletion operation {operation_key} {field} drift: expected {:?}, got {:?}", 7581 expected, actual 7582 )); 7583 } 7584 Ok(()) 7585 } 7586 7587 fn validate_food_availability_operation_authority( 7588 manifest: &OperationsContractManifest, 7589 workspace_root: &Path, 7590 ) -> Result<(), String> { 7591 let vector = validate_conformance_vector_file( 7592 &workspace_root.join(FOOD_AVAILABILITY_CONFORMANCE_VECTOR_RELATIVE), 7593 &manifest.contract.version, 7594 )?; 7595 validate_food_availability_operation_inventory(manifest, &vector) 7596 } 7597 7598 fn validate_food_availability_operation_inventory( 7599 manifest: &OperationsContractManifest, 7600 vector: &ConformanceVectorFile, 7601 ) -> Result<(), String> { 7602 let shared_types = collect_non_empty_set( 7603 &manifest.shared_types.public, 7604 "food availability operation shared_types.public", 7605 )?; 7606 for required in REQUIRED_FOOD_AVAILABILITY_PUBLIC_TYPES { 7607 if !shared_types.contains(required) { 7608 return Err(format!( 7609 "food availability operation authority requires shared public type {required}" 7610 )); 7611 } 7612 } 7613 7614 let expected_keys = FOOD_AVAILABILITY_OPERATION_EXPECTATIONS 7615 .iter() 7616 .map(|expectation| expectation.key.to_string()) 7617 .collect::<BTreeSet<_>>(); 7618 let actual_keys = manifest 7619 .operations 7620 .iter() 7621 .filter(|(key, operation)| { 7622 operation.domain == "food_availability" 7623 || operation.conformance.vector == FOOD_AVAILABILITY_CONFORMANCE_VECTOR_RELATIVE 7624 || key.starts_with("food_availability_") 7625 || operation.id.starts_with("food_availability.") 7626 }) 7627 .map(|(key, _)| key.clone()) 7628 .collect::<BTreeSet<_>>(); 7629 if actual_keys != expected_keys { 7630 let missing = expected_keys 7631 .difference(&actual_keys) 7632 .cloned() 7633 .collect::<BTreeSet<_>>(); 7634 let unexpected = actual_keys 7635 .difference(&expected_keys) 7636 .cloned() 7637 .collect::<BTreeSet<_>>(); 7638 return Err(format!( 7639 "food availability operation authority drift: missing {}; unexpected {}", 7640 join_set(&missing), 7641 join_set(&unexpected) 7642 )); 7643 } 7644 7645 let mut owners = BTreeMap::new(); 7646 for expected in FOOD_AVAILABILITY_OPERATION_EXPECTATIONS { 7647 let operation = manifest 7648 .operations 7649 .get(expected.key) 7650 .ok_or_else(|| format!("food availability operation {} is required", expected.key))?; 7651 validate_food_availability_operation_scalar( 7652 expected.key, 7653 "domain", 7654 &operation.domain, 7655 "food_availability", 7656 )?; 7657 validate_food_availability_operation_scalar( 7658 expected.key, 7659 "id", 7660 &operation.id, 7661 expected.id, 7662 )?; 7663 validate_food_availability_operation_scalar( 7664 expected.key, 7665 "stability", 7666 &operation.stability, 7667 "beta", 7668 )?; 7669 validate_food_availability_operation_scalar( 7670 expected.key, 7671 "error_class", 7672 &operation.error_class, 7673 expected.error_class, 7674 )?; 7675 validate_food_availability_operation_scalar( 7676 expected.key, 7677 "signing", 7678 &operation.signing, 7679 expected.signing, 7680 )?; 7681 validate_food_availability_operation_scalar( 7682 expected.key, 7683 "transport", 7684 &operation.transport, 7685 "none", 7686 )?; 7687 if !operation.deterministic { 7688 return Err(format!( 7689 "food availability operation {} deterministic drift: expected true, got false", 7690 expected.key 7691 )); 7692 } 7693 validate_food_availability_operation_sequence( 7694 expected.key, 7695 "inputs", 7696 &operation.inputs, 7697 expected.inputs, 7698 )?; 7699 validate_food_availability_operation_sequence( 7700 expected.key, 7701 "outputs", 7702 &operation.outputs, 7703 expected.outputs, 7704 )?; 7705 validate_food_availability_operation_sequence( 7706 expected.key, 7707 "implementation.rust_modules", 7708 &operation.implementation.rust_modules, 7709 expected.rust_modules, 7710 )?; 7711 validate_food_availability_operation_sequence( 7712 expected.key, 7713 "implementation.rust_types", 7714 &operation.implementation.rust_types, 7715 expected.rust_types, 7716 )?; 7717 validate_food_availability_operation_scalar( 7718 expected.key, 7719 "conformance.vector", 7720 &operation.conformance.vector, 7721 FOOD_AVAILABILITY_CONFORMANCE_VECTOR_RELATIVE, 7722 )?; 7723 validate_operation_case_kinds(operation, vector)?; 7724 if !operation 7725 .conformance 7726 .case_kinds 7727 .iter() 7728 .map(String::as_str) 7729 .eq(expected.case_kinds.iter().copied()) 7730 { 7731 return Err(format!( 7732 "food availability operation {} conformance.case_kinds drift: expected {:?}, got {:?}", 7733 expected.key, expected.case_kinds, operation.conformance.case_kinds 7734 )); 7735 } 7736 for case_kind in &operation.conformance.case_kinds { 7737 if let Some(previous) = owners.insert(case_kind.as_str(), expected.key) { 7738 return Err(format!( 7739 "food availability conformance case kind {case_kind} is multiply claimed by {previous} and {}", 7740 expected.key 7741 )); 7742 } 7743 } 7744 } 7745 7746 let expected_case_kinds = FOOD_AVAILABILITY_CASE_KINDS 7747 .into_iter() 7748 .collect::<BTreeSet<_>>(); 7749 let actual_case_kinds = owners.keys().copied().collect::<BTreeSet<_>>(); 7750 if actual_case_kinds != expected_case_kinds { 7751 return Err(format!( 7752 "food availability conformance case-kind authority drift: expected {:?}, got {:?}", 7753 expected_case_kinds, actual_case_kinds 7754 )); 7755 } 7756 7757 let mut actual_inventory = BTreeMap::new(); 7758 for entry in &vector.vectors { 7759 if actual_inventory 7760 .insert(entry.id.as_str(), entry.kind.as_str()) 7761 .is_some() 7762 { 7763 return Err(format!( 7764 "food availability conformance vector inventory has duplicate id {}", 7765 entry.id 7766 )); 7767 } 7768 } 7769 for kind in actual_inventory.values() { 7770 if !owners.contains_key(kind) { 7771 return Err(format!( 7772 "food availability conformance vector kind {kind} is not claimed by exactly one operation" 7773 )); 7774 } 7775 } 7776 let expected_inventory = FOOD_AVAILABILITY_VECTOR_EXPECTATIONS 7777 .into_iter() 7778 .collect::<BTreeMap<_, _>>(); 7779 if actual_inventory != expected_inventory { 7780 return Err(format!( 7781 "food availability conformance vector inventory drift: expected {:?}, got {:?}", 7782 expected_inventory, actual_inventory 7783 )); 7784 } 7785 Ok(()) 7786 } 7787 7788 fn validate_food_availability_operation_scalar( 7789 operation_key: &str, 7790 field: &str, 7791 actual: &str, 7792 expected: &str, 7793 ) -> Result<(), String> { 7794 if actual != expected { 7795 return Err(format!( 7796 "food availability operation {operation_key} {field} drift: expected {expected}, got {actual}" 7797 )); 7798 } 7799 Ok(()) 7800 } 7801 7802 fn validate_food_availability_operation_sequence( 7803 operation_key: &str, 7804 field: &str, 7805 actual: &[String], 7806 expected: &[&str], 7807 ) -> Result<(), String> { 7808 if !actual 7809 .iter() 7810 .map(String::as_str) 7811 .eq(expected.iter().copied()) 7812 { 7813 return Err(format!( 7814 "food availability operation {operation_key} {field} drift: expected {:?}, got {:?}", 7815 expected, actual 7816 )); 7817 } 7818 Ok(()) 7819 } 7820 7821 fn validate_calendar_operation_authority( 7822 manifest: &OperationsContractManifest, 7823 shared_types: &BTreeSet<String>, 7824 ) -> Result<(), String> { 7825 for required in REQUIRED_CALENDAR_PUBLIC_TYPES { 7826 if !shared_types.contains(required) { 7827 return Err(format!( 7828 "calendar operation authority requires shared public type {required}" 7829 )); 7830 } 7831 } 7832 7833 let expected_keys = CALENDAR_OPERATION_EXPECTATIONS 7834 .iter() 7835 .map(|expectation| expectation.key.to_string()) 7836 .collect::<BTreeSet<_>>(); 7837 let actual_keys = manifest 7838 .operations 7839 .iter() 7840 .filter(|(key, operation)| { 7841 key.starts_with("social_calendar_") || operation.id.starts_with("social.calendar") 7842 }) 7843 .map(|(key, _)| key.clone()) 7844 .collect::<BTreeSet<_>>(); 7845 if actual_keys != expected_keys { 7846 let missing = expected_keys 7847 .difference(&actual_keys) 7848 .cloned() 7849 .collect::<BTreeSet<_>>(); 7850 let unexpected = actual_keys 7851 .difference(&expected_keys) 7852 .cloned() 7853 .collect::<BTreeSet<_>>(); 7854 return Err(format!( 7855 "calendar operation authority drift: missing {}; unexpected {}", 7856 join_set(&missing), 7857 join_set(&unexpected) 7858 )); 7859 } 7860 7861 for expected in CALENDAR_OPERATION_EXPECTATIONS { 7862 let operation = manifest 7863 .operations 7864 .get(expected.key) 7865 .ok_or_else(|| format!("calendar operation {} is required", expected.key))?; 7866 validate_calendar_operation_scalar(expected.key, "domain", &operation.domain, "social")?; 7867 validate_calendar_operation_scalar(expected.key, "id", &operation.id, expected.id)?; 7868 validate_calendar_operation_scalar( 7869 expected.key, 7870 "stability", 7871 &operation.stability, 7872 "beta", 7873 )?; 7874 validate_calendar_operation_scalar( 7875 expected.key, 7876 "error_class", 7877 &operation.error_class, 7878 expected.error_class, 7879 )?; 7880 validate_calendar_operation_scalar(expected.key, "signing", &operation.signing, "none")?; 7881 validate_calendar_operation_scalar( 7882 expected.key, 7883 "transport", 7884 &operation.transport, 7885 "none", 7886 )?; 7887 if !operation.deterministic { 7888 return Err(format!( 7889 "calendar operation {} deterministic drift: expected true, got false", 7890 expected.key 7891 )); 7892 } 7893 validate_calendar_operation_sequence( 7894 expected.key, 7895 "inputs", 7896 &operation.inputs, 7897 expected.inputs, 7898 )?; 7899 validate_calendar_operation_sequence( 7900 expected.key, 7901 "outputs", 7902 &operation.outputs, 7903 expected.outputs, 7904 )?; 7905 validate_calendar_operation_sequence( 7906 expected.key, 7907 "implementation.rust_modules", 7908 &operation.implementation.rust_modules, 7909 expected.rust_modules, 7910 )?; 7911 validate_calendar_operation_sequence( 7912 expected.key, 7913 "implementation.rust_types", 7914 &operation.implementation.rust_types, 7915 expected.rust_types, 7916 )?; 7917 validate_calendar_operation_scalar( 7918 expected.key, 7919 "conformance.vector", 7920 &operation.conformance.vector, 7921 expected.vector, 7922 )?; 7923 } 7924 7925 Ok(()) 7926 } 7927 7928 fn validate_calendar_operation_scalar( 7929 operation_key: &str, 7930 field: &str, 7931 actual: &str, 7932 expected: &str, 7933 ) -> Result<(), String> { 7934 if actual != expected { 7935 return Err(format!( 7936 "calendar operation {operation_key} {field} drift: expected {expected}, got {actual}" 7937 )); 7938 } 7939 Ok(()) 7940 } 7941 7942 fn validate_calendar_operation_sequence( 7943 operation_key: &str, 7944 field: &str, 7945 actual: &[String], 7946 expected: &[&str], 7947 ) -> Result<(), String> { 7948 if !actual 7949 .iter() 7950 .map(String::as_str) 7951 .eq(expected.iter().copied()) 7952 { 7953 return Err(format!( 7954 "calendar operation {operation_key} {field} drift: expected {:?}, got {:?}", 7955 expected, actual 7956 )); 7957 } 7958 Ok(()) 7959 } 7960 7961 fn validate_no_retired_operation_event_names( 7962 values: &[String], 7963 context: &str, 7964 ) -> Result<(), String> { 7965 for value in values { 7966 for retired in RETIRED_OPERATION_EVENT_NAMES { 7967 if value == retired || value.ends_with(&format!("::{retired}")) { 7968 return Err(format!( 7969 "{context} uses retired event type {retired}; use target-state event and wire names" 7970 )); 7971 } 7972 } 7973 } 7974 Ok(()) 7975 } 7976 7977 fn package_field_configured(table: &toml::value::Table, field: &str) -> bool { 7978 let Some(value) = table.get(field) else { 7979 return false; 7980 }; 7981 match value { 7982 toml::Value::String(raw) => !raw.trim().is_empty(), 7983 toml::Value::Array(values) => !values.is_empty(), 7984 toml::Value::Table(inner) => inner 7985 .get("workspace") 7986 .and_then(toml::Value::as_bool) 7987 .is_some_and(|configured| configured), 7988 _ => false, 7989 } 7990 } 7991 7992 fn package_string_array<'a>( 7993 package: &'a toml::value::Table, 7994 crate_name: &str, 7995 field: &str, 7996 ) -> Result<Vec<&'a str>, String> { 7997 let values = package 7998 .get(field) 7999 .and_then(toml::Value::as_array) 8000 .ok_or_else(|| format!("publish crate {crate_name} must define package.{field}"))?; 8001 if values.is_empty() { 8002 return Err(format!( 8003 "publish crate {crate_name} package.{field} must not be empty" 8004 )); 8005 } 8006 let mut resolved = Vec::with_capacity(values.len()); 8007 let mut unique = BTreeSet::new(); 8008 for value in values { 8009 let value = value.as_str().ok_or_else(|| { 8010 format!("publish crate {crate_name} package.{field} entries must be strings") 8011 })?; 8012 if value.trim().is_empty() { 8013 return Err(format!( 8014 "publish crate {crate_name} package.{field} entries must not be empty" 8015 )); 8016 } 8017 if !unique.insert(value) { 8018 return Err(format!( 8019 "publish crate {crate_name} package.{field} contains duplicate {value}" 8020 )); 8021 } 8022 resolved.push(value); 8023 } 8024 Ok(resolved) 8025 } 8026 8027 fn validate_package_file_matches( 8028 workspace_root: &Path, 8029 package_root: &Path, 8030 crate_name: &str, 8031 relative: &str, 8032 ) -> Result<(), String> { 8033 let package_path = package_root.join(relative); 8034 let root_path = workspace_root.join(relative); 8035 let package_bytes = fs::read(&package_path) 8036 .map_err(|error| format!("publish crate {crate_name} must include {relative}: {error}"))?; 8037 let root_bytes = 8038 fs::read(&root_path).map_err(|error| format!("read {}: {error}", root_path.display()))?; 8039 if package_bytes != root_bytes { 8040 return Err(format!( 8041 "publish crate {crate_name} {relative} must match the workspace license" 8042 )); 8043 } 8044 Ok(()) 8045 } 8046 8047 fn validate_publish_package_metadata( 8048 workspace_root: &Path, 8049 publish_crates: &BTreeSet<String>, 8050 ) -> Result<(), String> { 8051 let mut package_records = BTreeMap::new(); 8052 for record in workspace_package_records(workspace_root)? { 8053 if package_records 8054 .insert(record.name.clone(), record) 8055 .is_some() 8056 { 8057 return Err("duplicate workspace package name in package metadata map".to_string()); 8058 } 8059 } 8060 for crate_name in publish_crates { 8061 let record = match package_records.get(crate_name) { 8062 Some(record) => record, 8063 None => { 8064 return Err(format!( 8065 "publish crate {} has no workspace manifest", 8066 crate_name 8067 )); 8068 } 8069 }; 8070 let package = record 8071 .manifest_value 8072 .get("package") 8073 .and_then(toml::Value::as_table) 8074 .expect("workspace package records include [package] table"); 8075 8076 if !package_field_configured(package, "description") { 8077 return Err(format!( 8078 "publish crate {} must define a non-empty package.description", 8079 crate_name 8080 )); 8081 } 8082 for field in [ 8083 "authors", 8084 "version", 8085 "edition", 8086 "rust-version", 8087 "license", 8088 "repository", 8089 "homepage", 8090 "documentation", 8091 "readme", 8092 ] { 8093 if !package_field_configured(package, field) { 8094 return Err(format!( 8095 "publish crate {} must configure package.{}", 8096 crate_name, field 8097 )); 8098 } 8099 } 8100 8101 let expected_documentation = format!("https://docs.rs/{crate_name}"); 8102 if package.get("documentation").and_then(toml::Value::as_str) 8103 != Some(expected_documentation.as_str()) 8104 { 8105 return Err(format!( 8106 "publish crate {crate_name} package.documentation must be {expected_documentation}" 8107 )); 8108 } 8109 if package.get("license-file").is_some() { 8110 return Err(format!( 8111 "publish crate {crate_name} must use the workspace SPDX license expression" 8112 )); 8113 } 8114 8115 let keywords = package_string_array(package, crate_name, "keywords")?; 8116 if keywords.len() > 5 { 8117 return Err(format!( 8118 "publish crate {crate_name} package.keywords exceeds the crates.io limit of 5" 8119 )); 8120 } 8121 let categories = package_string_array(package, crate_name, "categories")?; 8122 if categories.len() > 5 { 8123 return Err(format!( 8124 "publish crate {crate_name} package.categories exceeds the crates.io limit of 5" 8125 )); 8126 } 8127 8128 let include = package_string_array(package, crate_name, "include")?; 8129 for required in [ 8130 "src/**", 8131 "tests/**", 8132 "README.md", 8133 "LICENSE-APACHE", 8134 "LICENSE-MIT", 8135 ] { 8136 if !include.contains(&required) { 8137 return Err(format!( 8138 "publish crate {crate_name} package.include must contain {required}" 8139 )); 8140 } 8141 } 8142 8143 let package_root = record 8144 .manifest_path 8145 .parent() 8146 .expect("workspace member manifest has a parent"); 8147 if !package_root.join("README.md").is_file() { 8148 return Err(format!( 8149 "publish crate {crate_name} must include a package-local README.md" 8150 )); 8151 } 8152 validate_package_file_matches(workspace_root, package_root, crate_name, "LICENSE-APACHE")?; 8153 validate_package_file_matches(workspace_root, package_root, crate_name, "LICENSE-MIT")?; 8154 8155 let docs_rs = package 8156 .get("metadata") 8157 .and_then(toml::Value::as_table) 8158 .and_then(|metadata| metadata.get("docs")) 8159 .and_then(toml::Value::as_table) 8160 .and_then(|docs| docs.get("rs")) 8161 .and_then(toml::Value::as_table) 8162 .ok_or_else(|| { 8163 format!("publish crate {crate_name} must define [package.metadata.docs.rs]") 8164 })?; 8165 if docs_rs 8166 .get("all-features") 8167 .and_then(toml::Value::as_bool) 8168 .unwrap_or(false) 8169 { 8170 return Err(format!( 8171 "publish crate {crate_name} docs.rs must use an intentional feature set" 8172 )); 8173 } 8174 let docs_features = docs_rs 8175 .get("features") 8176 .and_then(toml::Value::as_array) 8177 .ok_or_else(|| format!("publish crate {crate_name} docs.rs must define features"))?; 8178 let declared_features = record 8179 .manifest_value 8180 .get("features") 8181 .and_then(toml::Value::as_table); 8182 for feature in docs_features { 8183 let feature = feature.as_str().ok_or_else(|| { 8184 format!("publish crate {crate_name} docs.rs features must be strings") 8185 })?; 8186 if !declared_features.is_some_and(|features| features.contains_key(feature)) { 8187 return Err(format!( 8188 "publish crate {crate_name} docs.rs selects unknown feature {feature}" 8189 )); 8190 } 8191 } 8192 } 8193 Ok(()) 8194 } 8195 8196 fn parse_coverage_percent(raw: &str, field: &str, crate_name: &str) -> Result<f64, String> { 8197 match raw.parse::<f64>() { 8198 Ok(value) => Ok(value), 8199 Err(e) => Err(format!("parse {} for {}: {e}", field, crate_name)), 8200 } 8201 } 8202 8203 fn parse_branch_coverage_percent(raw: &str, crate_name: &str) -> Result<Option<f64>, String> { 8204 if raw == "unavailable" { 8205 return Ok(None); 8206 } 8207 parse_coverage_percent(raw, "branch", crate_name).map(Some) 8208 } 8209 8210 fn branch_coverage_fails(branch: Option<f64>, thresholds: CoverageThresholds) -> bool { 8211 match branch { 8212 Some(value) => value < thresholds.fail_under_branches, 8213 None => thresholds.require_branches, 8214 } 8215 } 8216 8217 fn branch_coverage_display(branch: Option<f64>) -> String { 8218 branch 8219 .map(|value| value.to_string()) 8220 .unwrap_or_else(|| "unavailable".to_string()) 8221 } 8222 8223 #[derive(Debug)] 8224 struct CoverageRefreshRow { 8225 status: String, 8226 exec: f64, 8227 func: f64, 8228 branch: Option<f64>, 8229 region: f64, 8230 report_path: PathBuf, 8231 } 8232 8233 #[derive(Debug, Deserialize)] 8234 struct CoverageGateReportForValidation { 8235 scope: String, 8236 thresholds: CoverageGateReportThresholdsForValidation, 8237 measured: CoverageGateReportMeasuredForValidation, 8238 result: CoverageGateReportResultForValidation, 8239 } 8240 8241 #[derive(Debug, Deserialize)] 8242 struct CoverageGateReportThresholdsForValidation { 8243 executable_lines: f64, 8244 functions: f64, 8245 regions: f64, 8246 branches: f64, 8247 branches_required: bool, 8248 } 8249 8250 #[derive(Debug, Deserialize)] 8251 struct CoverageGateReportMeasuredForValidation { 8252 executable_lines_percent: f64, 8253 functions_percent: f64, 8254 branches_percent: Option<f64>, 8255 branches_available: bool, 8256 summary_regions_percent: f64, 8257 } 8258 8259 #[derive(Debug, Deserialize)] 8260 struct CoverageGateReportResultForValidation { 8261 pass: bool, 8262 } 8263 8264 type CoverageRefreshRows = BTreeMap<String, CoverageRefreshRow>; 8265 8266 fn coverage_refresh_report_path( 8267 workspace_root: &Path, 8268 report_path: &Path, 8269 raw_report_path: &str, 8270 crate_name: &str, 8271 ) -> Result<PathBuf, String> { 8272 let trimmed = raw_report_path.trim(); 8273 if trimmed.is_empty() { 8274 return Err(format!( 8275 "coverage row for crate {} in {} must include a report path", 8276 crate_name, 8277 report_path.display() 8278 )); 8279 } 8280 let path = Path::new(trimmed); 8281 if path.is_absolute() { 8282 Ok(path.to_path_buf()) 8283 } else { 8284 Ok(workspace_root.join(path)) 8285 } 8286 } 8287 8288 fn load_coverage_refresh_rows(workspace_root: &Path) -> Result<CoverageRefreshRows, String> { 8289 let report_path = workspace_root 8290 .join("target") 8291 .join("coverage") 8292 .join("coverage-refresh.tsv"); 8293 let raw = match fs::read_to_string(&report_path) { 8294 Ok(raw) => raw, 8295 Err(e) => return Err(format!("read {}: {e}", report_path.display())), 8296 }; 8297 let mut rows = BTreeMap::new(); 8298 for line in raw.lines().skip(1) { 8299 let trimmed = line.trim(); 8300 if trimmed.is_empty() { 8301 continue; 8302 } 8303 let parts = trimmed.split('\t').collect::<Vec<_>>(); 8304 if parts.len() < 7 { 8305 return Err(format!( 8306 "coverage row must have at least 7 columns in {}: {}", 8307 report_path.display(), 8308 trimmed 8309 )); 8310 } 8311 let crate_name = parts[0].to_string(); 8312 let status = parts[1].to_string(); 8313 let exec = parse_coverage_percent(parts[2], "exec", &crate_name)?; 8314 let func = parse_coverage_percent(parts[3], "func", &crate_name)?; 8315 let branch = parse_branch_coverage_percent(parts[4], &crate_name)?; 8316 let region = parse_coverage_percent(parts[5], "region", &crate_name)?; 8317 let row_report_path = 8318 coverage_refresh_report_path(workspace_root, &report_path, parts[6], &crate_name)?; 8319 if rows 8320 .insert( 8321 crate_name.clone(), 8322 CoverageRefreshRow { 8323 status, 8324 exec, 8325 func, 8326 branch, 8327 region, 8328 report_path: row_report_path, 8329 }, 8330 ) 8331 .is_some() 8332 { 8333 return Err(format!( 8334 "duplicate coverage row for crate {} in {}", 8335 crate_name, 8336 report_path.display() 8337 )); 8338 } 8339 } 8340 Ok(rows) 8341 } 8342 8343 #[cfg_attr(not(test), allow(dead_code))] 8344 fn validate_required_coverage_summary( 8345 workspace_root: &Path, 8346 required_crates: &BTreeSet<String>, 8347 thresholds: CoverageThresholds, 8348 ) -> Result<(), String> { 8349 let rows = load_coverage_refresh_rows(workspace_root)?; 8350 for crate_name in required_crates { 8351 let row = rows.get(crate_name).ok_or_else(|| { 8352 format!( 8353 "required coverage crate {} missing from coverage-refresh.tsv", 8354 crate_name 8355 ) 8356 })?; 8357 if row.status != "pass" { 8358 return Err(format!( 8359 "required coverage crate {} has non-pass status {}", 8360 crate_name, row.status 8361 )); 8362 } 8363 if row.exec < thresholds.fail_under_exec_lines 8364 || row.func < thresholds.fail_under_functions 8365 || branch_coverage_fails(row.branch, thresholds) 8366 || row.region < thresholds.fail_under_regions 8367 { 8368 return Err(format!( 8369 "required coverage crate {} must satisfy coverage policy {},{},{},{}, found {}/{}/{}/{}", 8370 crate_name, 8371 thresholds.fail_under_exec_lines, 8372 thresholds.fail_under_functions, 8373 thresholds.fail_under_branches, 8374 thresholds.fail_under_regions, 8375 row.exec, 8376 row.func, 8377 branch_coverage_display(row.branch), 8378 row.region 8379 )); 8380 } 8381 } 8382 Ok(()) 8383 } 8384 8385 fn read_coverage_gate_report( 8386 path: &Path, 8387 crate_name: &str, 8388 ) -> Result<CoverageGateReportForValidation, String> { 8389 let raw = match fs::read_to_string(path) { 8390 Ok(raw) => raw, 8391 Err(e) => { 8392 return Err(format!( 8393 "read coverage gate report for {} at {}: {e}", 8394 crate_name, 8395 path.display() 8396 )); 8397 } 8398 }; 8399 serde_json::from_str::<CoverageGateReportForValidation>(&raw).map_err(|e| { 8400 format!( 8401 "parse coverage gate report for {} at {}: {e}", 8402 crate_name, 8403 path.display() 8404 ) 8405 }) 8406 } 8407 8408 fn coverage_percent_matches(left: f64, right: f64) -> bool { 8409 (left - right).abs() <= COVERAGE_REPORT_EPSILON 8410 } 8411 8412 fn coverage_branch_percent_matches(left: Option<f64>, right: Option<f64>) -> bool { 8413 match (left, right) { 8414 (Some(left), Some(right)) => coverage_percent_matches(left, right), 8415 (None, None) => true, 8416 _ => false, 8417 } 8418 } 8419 8420 fn coverage_gate_report_thresholds_match( 8421 report: &CoverageGateReportThresholdsForValidation, 8422 thresholds: CoverageThresholds, 8423 ) -> bool { 8424 coverage_percent_matches(report.executable_lines, thresholds.fail_under_exec_lines) 8425 && coverage_percent_matches(report.functions, thresholds.fail_under_functions) 8426 && coverage_percent_matches(report.regions, thresholds.fail_under_regions) 8427 && coverage_percent_matches(report.branches, thresholds.fail_under_branches) 8428 && report.branches_required == thresholds.require_branches 8429 } 8430 8431 fn validate_coverage_gate_report_for_row( 8432 crate_name: &str, 8433 row: &CoverageRefreshRow, 8434 thresholds: CoverageThresholds, 8435 ) -> Result<(), String> { 8436 let report = read_coverage_gate_report(&row.report_path, crate_name)?; 8437 if report.scope != crate_name { 8438 return Err(format!( 8439 "coverage gate report {} has scope {}, expected {}", 8440 row.report_path.display(), 8441 report.scope, 8442 crate_name 8443 )); 8444 } 8445 if !coverage_gate_report_thresholds_match(&report.thresholds, thresholds) { 8446 return Err(format!( 8447 "coverage gate report {} for {} thresholds do not match policy", 8448 row.report_path.display(), 8449 crate_name 8450 )); 8451 } 8452 if !report.result.pass { 8453 return Err(format!( 8454 "coverage gate report {} for {} has non-pass result", 8455 row.report_path.display(), 8456 crate_name 8457 )); 8458 } 8459 if report.measured.branches_available != report.measured.branches_percent.is_some() { 8460 return Err(format!( 8461 "coverage gate report {} for {} has inconsistent branch measurement", 8462 row.report_path.display(), 8463 crate_name 8464 )); 8465 } 8466 if !coverage_percent_matches(row.exec, report.measured.executable_lines_percent) 8467 || !coverage_percent_matches(row.func, report.measured.functions_percent) 8468 || !coverage_branch_percent_matches(row.branch, report.measured.branches_percent) 8469 || !coverage_percent_matches(row.region, report.measured.summary_regions_percent) 8470 { 8471 return Err(format!( 8472 "coverage row for {} does not match coverage gate report {}", 8473 crate_name, 8474 row.report_path.display() 8475 )); 8476 } 8477 Ok(()) 8478 } 8479 8480 fn validate_required_coverage_summary_with_policy( 8481 workspace_root: &Path, 8482 required_crates: &BTreeSet<String>, 8483 policy: &CoveragePolicyFile, 8484 ) -> Result<(), String> { 8485 let rows = load_coverage_refresh_rows(workspace_root)?; 8486 for crate_name in required_crates { 8487 let row = rows.get(crate_name).ok_or_else(|| { 8488 format!( 8489 "required coverage crate {} missing from coverage-refresh.tsv", 8490 crate_name 8491 ) 8492 })?; 8493 if row.status != "pass" { 8494 return Err(format!( 8495 "required coverage crate {} has non-pass status {}", 8496 crate_name, row.status 8497 )); 8498 } 8499 let thresholds = policy.thresholds_for_scope(crate_name); 8500 validate_coverage_gate_report_for_row(crate_name, row, thresholds)?; 8501 if row.exec < thresholds.fail_under_exec_lines 8502 || row.func < thresholds.fail_under_functions 8503 || branch_coverage_fails(row.branch, thresholds) 8504 || row.region < thresholds.fail_under_regions 8505 { 8506 return Err(format!( 8507 "required coverage crate {} must satisfy coverage policy {},{},{},{}, found {}/{}/{}/{}", 8508 crate_name, 8509 thresholds.fail_under_exec_lines, 8510 thresholds.fail_under_functions, 8511 thresholds.fail_under_branches, 8512 thresholds.fail_under_regions, 8513 row.exec, 8514 row.func, 8515 branch_coverage_display(row.branch), 8516 row.region 8517 )); 8518 } 8519 } 8520 Ok(()) 8521 } 8522 8523 const CORE_UNIT_DIMENSION_ENUM: &str = "UnitDimension"; 8524 const CORE_UNIT_DIMENSION_ORDER: [&str; 3] = ["Count", "Mass", "Volume"]; 8525 8526 fn extract_enum_body<'a>(source: &'a str, enum_name: &str) -> Result<&'a str, String> { 8527 let marker = format!("pub enum {enum_name}"); 8528 let enum_start = match source.find(&marker) { 8529 Some(index) => index, 8530 None => return Err(format!("missing enum {enum_name}")), 8531 }; 8532 let after_start = &source[enum_start..]; 8533 let open_rel = match after_start.find('{') { 8534 Some(index) => index, 8535 None => return Err(format!("missing opening brace for enum {enum_name}")), 8536 }; 8537 let open_idx = enum_start + open_rel; 8538 let mut depth = 0usize; 8539 for (offset, ch) in source[open_idx..].char_indices() { 8540 if ch == '{' { 8541 depth += 1; 8542 continue; 8543 } 8544 if ch != '}' { 8545 continue; 8546 } 8547 depth = depth.saturating_sub(1); 8548 if depth == 0 { 8549 let close_idx = open_idx + offset; 8550 return Ok(&source[(open_idx + 1)..close_idx]); 8551 } 8552 } 8553 Err(format!("missing closing brace for enum {enum_name}")) 8554 } 8555 8556 fn parse_enum_variants(enum_body: &str) -> Vec<String> { 8557 enum_body 8558 .lines() 8559 .filter_map(|line| { 8560 let trimmed = line.trim(); 8561 if trimmed.is_empty() || trimmed.starts_with('#') || trimmed.starts_with("//") { 8562 return None; 8563 } 8564 let before_comma = trimmed 8565 .split_once(',') 8566 .map_or(trimmed, |(head, _)| head) 8567 .trim(); 8568 if before_comma.is_empty() { 8569 return None; 8570 } 8571 let before_discriminant = before_comma 8572 .split_once('=') 8573 .map_or(before_comma, |(head, _)| head) 8574 .trim(); 8575 if before_discriminant.is_empty() { 8576 return None; 8577 } 8578 let ident = before_discriminant 8579 .split_whitespace() 8580 .next() 8581 .unwrap_or_default(); 8582 Some(ident.to_string()) 8583 }) 8584 .collect() 8585 } 8586 8587 fn validate_core_unit_dimension_variant_order(workspace_root: &Path) -> Result<(), String> { 8588 let source_path = workspace_root 8589 .join("crates") 8590 .join("core") 8591 .join("src") 8592 .join("unit.rs"); 8593 let source = match fs::read_to_string(&source_path) { 8594 Ok(source) => source, 8595 Err(e) => return Err(format!("read {}: {e}", source_path.display())), 8596 }; 8597 let enum_body = extract_enum_body(&source, CORE_UNIT_DIMENSION_ENUM)?; 8598 let variants = parse_enum_variants(enum_body); 8599 let expected = CORE_UNIT_DIMENSION_ORDER 8600 .iter() 8601 .map(|item| (*item).to_string()) 8602 .collect::<Vec<_>>(); 8603 if variants != expected { 8604 return Err(format!( 8605 "core unit dimension variant order must be {} but was {}", 8606 CORE_UNIT_DIMENSION_ORDER.join(", "), 8607 variants.join(", ") 8608 )); 8609 } 8610 Ok(()) 8611 } 8612 8613 fn validate_coverage_policy_parity( 8614 workspace_root: &Path, 8615 contract_root: &Path, 8616 ) -> Result<(), String> { 8617 let policy = load_coverage_policy(contract_root)?; 8618 let thresholds = policy.thresholds(); 8619 if thresholds.fail_under_exec_lines != COVERAGE_REQUIRED_THRESHOLD 8620 || thresholds.fail_under_functions != COVERAGE_REQUIRED_THRESHOLD 8621 || thresholds.fail_under_regions != COVERAGE_REQUIRED_THRESHOLD 8622 || thresholds.fail_under_branches != COVERAGE_REQUIRED_THRESHOLD 8623 || !thresholds.require_branches 8624 { 8625 return Err(format!( 8626 "coverage policy must enforce {COVERAGE_REQUIRED_THRESHOLD_LABEL} with required branches" 8627 )); 8628 } 8629 8630 let required_packages = policy 8631 .required_crate_entries() 8632 .iter() 8633 .cloned() 8634 .collect::<BTreeSet<_>>(); 8635 for package in &required_packages { 8636 let scoped = policy.thresholds_for_scope(package); 8637 if scoped.fail_under_exec_lines < COVERAGE_REQUIRED_THRESHOLD 8638 || scoped.fail_under_functions < COVERAGE_REQUIRED_THRESHOLD 8639 || scoped.fail_under_regions < COVERAGE_REQUIRED_THRESHOLD 8640 || scoped.fail_under_branches < COVERAGE_REQUIRED_THRESHOLD 8641 { 8642 return Err(format!( 8643 "coverage policy scope {package} must enforce at least {COVERAGE_REQUIRED_THRESHOLD_LABEL}" 8644 )); 8645 } 8646 } 8647 let expected_packages = coverage_required_workspace_crates(workspace_root)?; 8648 if expected_packages != required_packages { 8649 let missing = expected_packages 8650 .difference(&required_packages) 8651 .cloned() 8652 .collect::<BTreeSet<_>>(); 8653 let extra = required_packages 8654 .difference(&expected_packages) 8655 .cloned() 8656 .collect::<BTreeSet<_>>(); 8657 return Err(format!( 8658 "coverage policy missing workspace crates: {}; coverage policy includes excluded or unknown crates: {}", 8659 join_set(&missing), 8660 join_set(&extra) 8661 )); 8662 } 8663 8664 Ok(()) 8665 } 8666 8667 fn publish_config_is_public(publish: Option<&PackagePublish>) -> bool { 8668 matches!( 8669 publish, 8670 Some(PackagePublish::Registries(registries)) 8671 if registries.len() == 1 && registries[0] == "crates-io" 8672 ) 8673 } 8674 8675 fn publish_config_is_non_public(publish: Option<&PackagePublish>) -> bool { 8676 matches!(publish, Some(PackagePublish::Bool(false))) 8677 } 8678 8679 fn validate_publication_control( 8680 release: &ReleaseContractFile, 8681 publish_configs: &BTreeMap<String, Option<PackagePublish>>, 8682 require_control: bool, 8683 ) -> Result<bool, String> { 8684 let Some(control) = release.publication.as_ref() else { 8685 if require_control { 8686 return Err("publication control is required".to_string()); 8687 } 8688 return Ok(false); 8689 }; 8690 if control.registry != "crates-io" { 8691 return Err("publication.registry must be crates-io".to_string()); 8692 } 8693 if control.final_enablement_step != 305 { 8694 return Err("publication.final_enablement_step must be 305".to_string()); 8695 } 8696 if !control.frozen { 8697 return Ok(false); 8698 } 8699 for (crate_name, publish) in publish_configs { 8700 if !publish_config_is_non_public(publish.as_ref()) { 8701 return Err(format!( 8702 "publication freeze requires workspace crate {} to set publish = false", 8703 crate_name 8704 )); 8705 } 8706 } 8707 Ok(true) 8708 } 8709 8710 fn validate_v1_release_policy( 8711 workspace_root: &Path, 8712 release: &ReleaseContractFile, 8713 workspace_packages: &BTreeSet<String>, 8714 publish_configs: &BTreeMap<String, Option<PackagePublish>>, 8715 require_v1: bool, 8716 ) -> Result<Option<BTreeSet<String>>, String> { 8717 let Some(control) = release.publication.as_ref() else { 8718 if require_v1 { 8719 return Err("publication control is required".to_string()); 8720 } 8721 return Ok(None); 8722 }; 8723 let declares_v1 = !control.spec_id.is_empty() 8724 || !control.approved_packages.is_empty() 8725 || !control.local_packages.is_empty() 8726 || !control.external_packages.is_empty() 8727 || release.workspace_classification.is_some(); 8728 if !declares_v1 { 8729 if require_v1 { 8730 return Err("publication must define the v1 approved package authority".to_string()); 8731 } 8732 return Ok(None); 8733 } 8734 8735 let architecture_path = 8736 workspace_root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"); 8737 let architecture = parse_toml::<CratesReleaseArchitecture>(&architecture_path)?; 8738 let expected_approved = collect_unique_set( 8739 &architecture 8740 .package 8741 .iter() 8742 .map(|package| package.name.clone()) 8743 .collect::<Vec<_>>(), 8744 "architecture.package.name", 8745 )?; 8746 if architecture.package_count != expected_approved.len() || architecture.package_count != 19 { 8747 return Err(format!( 8748 "release architecture must define exactly 19 unique packages, found package_count {} and {} unique package records", 8749 architecture.package_count, 8750 expected_approved.len() 8751 )); 8752 } 8753 if control.spec_id != architecture.spec_id || control.spec_id != "radroots.crates.release.v1" { 8754 return Err(format!( 8755 "publication.spec_id {} must match architecture id {}", 8756 control.spec_id, architecture.spec_id 8757 )); 8758 } 8759 8760 let approved = collect_unique_set(&control.approved_packages, "publication.approved_packages")?; 8761 let local = collect_unique_set(&control.local_packages, "publication.local_packages")?; 8762 let external = collect_unique_set(&control.external_packages, "publication.external_packages")?; 8763 let legacy_local = collect_unique_set( 8764 &architecture.repositories.lib.packages, 8765 "architecture.repositories.lib.packages", 8766 )?; 8767 let legacy_external = collect_unique_set( 8768 &architecture.repositories.sdk.packages, 8769 "architecture.repositories.sdk.packages", 8770 )?; 8771 let consolidation_path = workspace_root.join("contracts/consolidation/architecture.v1.toml"); 8772 let workspace = 8773 parse_toml::<WorkspaceVersionCargoManifest>(&workspace_root.join("Cargo.toml"))?; 8774 let consolidated_here = if consolidation_path.is_file() { 8775 let consolidation = parse_toml::<ConsolidationReleaseOwnership>(&consolidation_path)?; 8776 workspace.workspace.package.repository == consolidation.canonical_rust_repository 8777 } else { 8778 false 8779 }; 8780 let (expected_local, expected_external) = if consolidated_here { 8781 (expected_approved.clone(), BTreeSet::new()) 8782 } else { 8783 (legacy_local, legacy_external) 8784 }; 8785 for (field, actual, expected) in [ 8786 ( 8787 "publication.approved_packages", 8788 &approved, 8789 &expected_approved, 8790 ), 8791 ("publication.local_packages", &local, &expected_local), 8792 ( 8793 "publication.external_packages", 8794 &external, 8795 &expected_external, 8796 ), 8797 ] { 8798 if actual != expected { 8799 let missing = expected 8800 .difference(actual) 8801 .cloned() 8802 .collect::<BTreeSet<_>>(); 8803 let extra = actual 8804 .difference(expected) 8805 .cloned() 8806 .collect::<BTreeSet<_>>(); 8807 return Err(format!( 8808 "{field} is missing approved packages: {}; {field} has unapproved packages: {}", 8809 join_set(&missing), 8810 join_set(&extra) 8811 )); 8812 } 8813 } 8814 let ownership_overlap = local 8815 .intersection(&external) 8816 .cloned() 8817 .collect::<BTreeSet<_>>(); 8818 if !ownership_overlap.is_empty() { 8819 return Err(format!( 8820 "local and external approved package ownership overlaps: {}", 8821 join_set(&ownership_overlap) 8822 )); 8823 } 8824 let mut owned = local.clone(); 8825 owned.extend(external.iter().cloned()); 8826 if owned != approved { 8827 return Err( 8828 "local and external package ownership must partition approved packages".to_string(), 8829 ); 8830 } 8831 let external_in_workspace = external 8832 .intersection(workspace_packages) 8833 .cloned() 8834 .collect::<BTreeSet<_>>(); 8835 if !external_in_workspace.is_empty() { 8836 return Err(format!( 8837 "externally owned approved packages must not be workspace members: {}", 8838 join_set(&external_in_workspace) 8839 )); 8840 } 8841 8842 let classification = release.workspace_classification.as_ref().ok_or_else(|| { 8843 "workspace_classification is required for the v1 release policy".to_string() 8844 })?; 8845 let private = collect_unique_set(&classification.private, "workspace_classification.private")?; 8846 let build_codegen = collect_unique_set( 8847 &classification.build_codegen, 8848 "workspace_classification.build_codegen", 8849 )?; 8850 let test_support = collect_unique_set( 8851 &classification.test_support, 8852 "workspace_classification.test_support", 8853 )?; 8854 let preview = collect_unique_set(&classification.preview, "workspace_classification.preview")?; 8855 let retired = collect_unique_set(&classification.retired, "workspace_classification.retired")?; 8856 let classes = [ 8857 ("private", &private), 8858 ("build-codegen", &build_codegen), 8859 ("test-support", &test_support), 8860 ("preview", &preview), 8861 ("retired", &retired), 8862 ]; 8863 for index in 0..classes.len() { 8864 for other_index in (index + 1)..classes.len() { 8865 let overlap = classes[index] 8866 .1 8867 .intersection(classes[other_index].1) 8868 .cloned() 8869 .collect::<BTreeSet<_>>(); 8870 if !overlap.is_empty() { 8871 return Err(format!( 8872 "workspace classification overlap is not allowed between {} and {}: {}", 8873 classes[index].0, 8874 classes[other_index].0, 8875 join_set(&overlap) 8876 )); 8877 } 8878 } 8879 } 8880 let mut classified = BTreeSet::new(); 8881 for (_, entries) in classes { 8882 classified.extend(entries.iter().cloned()); 8883 } 8884 let local_workspace_packages = local 8885 .intersection(workspace_packages) 8886 .cloned() 8887 .collect::<BTreeSet<_>>(); 8888 let public_classification_overlap = classified 8889 .intersection(&local_workspace_packages) 8890 .cloned() 8891 .collect::<BTreeSet<_>>(); 8892 if !public_classification_overlap.is_empty() { 8893 return Err(format!( 8894 "approved local packages must not be classified as private workspace packages: {}", 8895 join_set(&public_classification_overlap) 8896 )); 8897 } 8898 let mut accounted = classified.clone(); 8899 accounted.extend(local_workspace_packages.iter().cloned()); 8900 if accounted != *workspace_packages { 8901 let missing = workspace_packages 8902 .difference(&accounted) 8903 .cloned() 8904 .collect::<BTreeSet<_>>(); 8905 let extra = accounted 8906 .difference(workspace_packages) 8907 .cloned() 8908 .collect::<BTreeSet<_>>(); 8909 return Err(format!( 8910 "workspace classification is missing packages: {}; workspace classification has unknown packages: {}", 8911 join_set(&missing), 8912 join_set(&extra) 8913 )); 8914 } 8915 8916 if control.registry != "crates-io" { 8917 return Err("publication.registry must be crates-io".to_string()); 8918 } 8919 if control.final_enablement_step != 305 { 8920 return Err("publication.final_enablement_step must be 305".to_string()); 8921 } 8922 let publish_order = collect_unique_set(&release.publish_order.crates, "publish_order.crates")?; 8923 if control.frozen { 8924 if !publish_order.is_empty() { 8925 return Err( 8926 "publish_order.crates must remain empty while publication is frozen".to_string(), 8927 ); 8928 } 8929 for (crate_name, publish) in publish_configs { 8930 if !publish_config_is_non_public(publish.as_ref()) { 8931 return Err(format!( 8932 "publication freeze requires workspace crate {} to set publish = false", 8933 crate_name 8934 )); 8935 } 8936 } 8937 return Ok(Some(BTreeSet::new())); 8938 } 8939 8940 if local_workspace_packages != local { 8941 let missing = local 8942 .difference(&local_workspace_packages) 8943 .cloned() 8944 .collect::<BTreeSet<_>>(); 8945 return Err(format!( 8946 "publication enablement is missing approved local workspace packages: {}", 8947 join_set(&missing) 8948 )); 8949 } 8950 if publish_order != local { 8951 return Err("publish_order.crates must contain exactly the approved local packages when publication is enabled".to_string()); 8952 } 8953 for (crate_name, publish) in publish_configs { 8954 if local.contains(crate_name) { 8955 if !publish_config_is_public(publish.as_ref()) { 8956 return Err(format!( 8957 "approved local crate {} must set publish = [\"crates-io\"]", 8958 crate_name 8959 )); 8960 } 8961 } else if !publish_config_is_non_public(publish.as_ref()) { 8962 return Err(format!( 8963 "private workspace crate {} must set publish = false", 8964 crate_name 8965 )); 8966 } 8967 } 8968 Ok(Some(local)) 8969 } 8970 8971 #[cfg(test)] 8972 fn validate_release_publish_policy( 8973 workspace_root: &Path, 8974 _contract_root: &Path, 8975 contract_version: &str, 8976 ) -> Result<(), String> { 8977 let release = load_release_contract(workspace_root, contract_version)?; 8978 if release.release.version.trim().is_empty() { 8979 return Err("release.version must not be empty".to_string()); 8980 } 8981 if release.release.version != contract_version { 8982 return Err(format!( 8983 "release.version {} must match contract version {}", 8984 release.release.version, contract_version 8985 )); 8986 } 8987 8988 let workspace_packages = workspace_package_names(workspace_root)? 8989 .into_iter() 8990 .collect::<BTreeSet<_>>(); 8991 let publish_configs = workspace_package_publish_configs(workspace_root) 8992 .expect("workspace publish configs are stable"); 8993 if validate_v1_release_policy( 8994 workspace_root, 8995 &release, 8996 &workspace_packages, 8997 &publish_configs, 8998 false, 8999 )? 9000 .is_some() 9001 { 9002 return Ok(()); 9003 } 9004 let uses_classification = release.uses_classification(); 9005 let public_field = if uses_classification { 9006 "classification.public" 9007 } else { 9008 "publish.crates" 9009 }; 9010 let internal_field = if uses_classification { 9011 "classification.internal" 9012 } else { 9013 "internal.crates" 9014 }; 9015 9016 let public_set = collect_unique_set(&release.public_crates(), public_field)?; 9017 let internal_set = collect_unique_set(&release.internal_crates(), internal_field)?; 9018 let deferred_set = collect_unique_set(&release.deferred_crates(), "classification.deferred")?; 9019 let retired_set = collect_unique_set(&release.retired_crates(), "classification.retired")?; 9020 let yank_only_set = 9021 collect_unique_set(&release.yank_only_crates(), "classification.yank_only")?; 9022 let publish_order = &release.publish_order.crates; 9023 let publish_order_set = collect_unique_set(publish_order, "publish_order.crates")?; 9024 9025 let class_sets = [ 9026 ("public", &public_set), 9027 ("internal", &internal_set), 9028 ("deferred", &deferred_set), 9029 ("retired", &retired_set), 9030 ("yank-only", &yank_only_set), 9031 ]; 9032 for idx in 0..class_sets.len() { 9033 for other_idx in (idx + 1)..class_sets.len() { 9034 let overlap = class_sets[idx] 9035 .1 9036 .intersection(class_sets[other_idx].1) 9037 .cloned() 9038 .collect::<BTreeSet<_>>(); 9039 if !overlap.is_empty() { 9040 return Err(format!( 9041 "release classification overlap is not allowed between {} and {}: {}", 9042 class_sets[idx].0, 9043 class_sets[other_idx].0, 9044 join_set(&overlap) 9045 )); 9046 } 9047 } 9048 } 9049 9050 let mut combined = public_set.clone(); 9051 combined.extend(internal_set.iter().cloned()); 9052 combined.extend(deferred_set.iter().cloned()); 9053 combined.extend(retired_set.iter().cloned()); 9054 combined.extend(yank_only_set.iter().cloned()); 9055 if combined != workspace_packages { 9056 let missing = workspace_packages 9057 .difference(&combined) 9058 .cloned() 9059 .collect::<BTreeSet<_>>(); 9060 let extra = combined 9061 .difference(&workspace_packages) 9062 .cloned() 9063 .collect::<BTreeSet<_>>(); 9064 return Err(format!( 9065 "release classification sets are missing workspace crates: {}; release classification sets include unknown crates: {}", 9066 join_set(&missing), 9067 join_set(&extra) 9068 )); 9069 } 9070 9071 if publish_order_set != public_set { 9072 let missing = public_set 9073 .difference(&publish_order_set) 9074 .cloned() 9075 .collect::<BTreeSet<_>>(); 9076 let extra = publish_order_set 9077 .difference(&public_set) 9078 .cloned() 9079 .collect::<BTreeSet<_>>(); 9080 return Err(format!( 9081 "publish_order.crates is missing publish crates: {}; publish_order.crates has non-publish crates: {}", 9082 join_set(&missing), 9083 join_set(&extra) 9084 )); 9085 } 9086 9087 let order_index = publish_order 9088 .iter() 9089 .enumerate() 9090 .map(|(idx, name)| (name.clone(), idx)) 9091 .collect::<BTreeMap<_, _>>(); 9092 let dependencies = read_workspace_package_dependencies(workspace_root) 9093 .expect("workspace package manifests were already parsed"); 9094 for crate_name in &public_set { 9095 let crate_deps = &dependencies[crate_name]; 9096 let crate_order = order_index[crate_name]; 9097 for dep in crate_deps { 9098 if !public_set.contains(dep) { 9099 continue; 9100 } 9101 let dep_order = order_index[dep]; 9102 if dep_order >= crate_order { 9103 return Err(format!( 9104 "publish order must place dependency {} before {}", 9105 dep, crate_name 9106 )); 9107 } 9108 } 9109 } 9110 9111 if validate_publication_control(&release, &publish_configs, false)? { 9112 return Ok(()); 9113 } 9114 for crate_name in &public_set { 9115 let publish = publish_configs[crate_name].as_ref(); 9116 if !publish_config_is_public(publish) { 9117 return Err(format!( 9118 "public crate {} must set publish = [\"crates-io\"]", 9119 crate_name 9120 )); 9121 } 9122 } 9123 for crate_name in internal_set 9124 .iter() 9125 .chain(deferred_set.iter()) 9126 .chain(retired_set.iter()) 9127 .chain(yank_only_set.iter()) 9128 { 9129 let publish = publish_configs[crate_name].as_ref(); 9130 if !publish_config_is_non_public(publish) { 9131 return Err(format!( 9132 "non-public crate {} must set publish = false", 9133 crate_name 9134 )); 9135 } 9136 } 9137 Ok(()) 9138 } 9139 9140 #[derive(Clone, Copy)] 9141 enum OperationAuthorityProfile { 9142 CapsuleCanonical, 9143 #[cfg(test)] 9144 Generic, 9145 } 9146 9147 pub fn validate_release_preflight(workspace_root: &Path) -> Result<(), String> { 9148 validate_release_preflight_with_override(workspace_root, None) 9149 } 9150 9151 pub fn validate_release_preflight_with_override( 9152 workspace_root: &Path, 9153 release_policy_override: Option<PathBuf>, 9154 ) -> Result<(), String> { 9155 validate_release_preflight_with_override_and_profile( 9156 workspace_root, 9157 release_policy_override, 9158 OperationAuthorityProfile::CapsuleCanonical, 9159 ) 9160 } 9161 9162 fn validate_release_preflight_with_override_and_profile( 9163 workspace_root: &Path, 9164 release_policy_override: Option<PathBuf>, 9165 authority_profile: OperationAuthorityProfile, 9166 ) -> Result<(), String> { 9167 let bundle = load_contract_bundle(workspace_root)?; 9168 validate_contract_bundle_with_release_policy_override_and_profile( 9169 &bundle, 9170 release_policy_override.clone(), 9171 authority_profile, 9172 )?; 9173 let release = load_release_contract_with_override( 9174 workspace_root, 9175 bundle.version.contract.version.as_str(), 9176 release_policy_override, 9177 )?; 9178 let policy = 9179 load_coverage_policy(&bundle.root).expect("validated contract includes coverage policy"); 9180 let publish_crates = collect_unique_set( 9181 &release.public_crates(), 9182 if release.uses_classification() { 9183 "classification.public" 9184 } else { 9185 "publish.crates" 9186 }, 9187 ) 9188 .expect("validated contract enforces unique public crates"); 9189 let required_crate_list = policy 9190 .required_crates() 9191 .expect("validated contract includes required crates"); 9192 let required_crates = collect_unique_set(&required_crate_list, "required.crates") 9193 .expect("validated contract enforces unique required.crates"); 9194 validate_publishable_dto_tooling_sources(workspace_root, &publish_crates)?; 9195 validate_publish_package_metadata(workspace_root, &publish_crates)?; 9196 validate_required_coverage_summary_with_policy(workspace_root, &required_crates, &policy)?; 9197 Ok(()) 9198 } 9199 9200 fn validate_contract_bundle_with_release_policy_override( 9201 bundle: &ContractBundle, 9202 release_policy_override: Option<PathBuf>, 9203 ) -> Result<(), String> { 9204 validate_contract_bundle_with_release_policy_override_and_profile( 9205 bundle, 9206 release_policy_override, 9207 OperationAuthorityProfile::CapsuleCanonical, 9208 ) 9209 } 9210 9211 fn validate_contract_bundle_with_release_policy_override_and_profile( 9212 bundle: &ContractBundle, 9213 release_policy_override: Option<PathBuf>, 9214 authority_profile: OperationAuthorityProfile, 9215 ) -> Result<(), String> { 9216 if bundle.manifest.contract.name.trim().is_empty() { 9217 return Err("contract name is required".to_string()); 9218 } 9219 if bundle.manifest.contract.version.trim().is_empty() { 9220 return Err("contract version is required".to_string()); 9221 } 9222 if bundle.manifest.contract.source.trim().is_empty() { 9223 return Err("contract source is required".to_string()); 9224 } 9225 if bundle.manifest.surface.model_crates.is_empty() { 9226 return Err("contract surface.model_crates must not be empty".to_string()); 9227 } 9228 if bundle.manifest.surface.algorithm_crates.is_empty() { 9229 return Err("contract surface.algorithm_crates must not be empty".to_string()); 9230 } 9231 validate_surface_metadata(&bundle.manifest.surface)?; 9232 if bundle.version.contract.version.trim().is_empty() { 9233 return Err("version.contract.version is required".to_string()); 9234 } 9235 if bundle.version.contract.stability.trim().is_empty() { 9236 return Err("version.contract.stability is required".to_string()); 9237 } 9238 if bundle.version.semver.major_on.is_empty() 9239 || bundle.version.semver.minor_on.is_empty() 9240 || bundle.version.semver.patch_on.is_empty() 9241 { 9242 return Err("version.semver rules must all be non-empty".to_string()); 9243 } 9244 if !bundle.version.release_integrity.requires_conformance_pass { 9245 return Err("release_integrity.requires_conformance_pass must be true".to_string()); 9246 } 9247 if !bundle 9248 .version 9249 .release_integrity 9250 .requires_contract_manifest_diff 9251 { 9252 return Err("release_integrity.requires_contract_manifest_diff must be true".to_string()); 9253 } 9254 if !bundle.version.release_integrity.requires_release_notes { 9255 return Err("release_integrity.requires_release_notes must be true".to_string()); 9256 } 9257 validate_policy_metadata(&bundle.manifest.policy)?; 9258 let workspace_root = bundle 9259 .root 9260 .parent() 9261 .expect("contract root must have a workspace parent"); 9262 validate_replica_contract(bundle, workspace_root)?; 9263 validate_operations_contract(bundle, &bundle.operations_manifest, workspace_root)?; 9264 if matches!( 9265 authority_profile, 9266 OperationAuthorityProfile::CapsuleCanonical 9267 ) { 9268 validate_capsule_operation_authority(&bundle.operations_manifest, workspace_root)?; 9269 } 9270 validate_all_conformance_vectors(workspace_root, &bundle.manifest.contract.version)?; 9271 validate_core_unit_dimension_variant_order(workspace_root)?; 9272 validate_coverage_policy_parity(workspace_root, &bundle.root)?; 9273 validate_version_governance(bundle, workspace_root)?; 9274 if matches!( 9275 authority_profile, 9276 OperationAuthorityProfile::CapsuleCanonical 9277 ) { 9278 crate::architecture::validate(workspace_root)?; 9279 } 9280 validate_release_publish_policy_with_override_and_control( 9281 workspace_root, 9282 &bundle.root, 9283 bundle.version.contract.version.as_str(), 9284 release_policy_override, 9285 matches!( 9286 authority_profile, 9287 OperationAuthorityProfile::CapsuleCanonical 9288 ), 9289 matches!( 9290 authority_profile, 9291 OperationAuthorityProfile::CapsuleCanonical 9292 ), 9293 )?; 9294 Ok(()) 9295 } 9296 9297 #[cfg(test)] 9298 fn validate_release_publish_policy_with_override( 9299 workspace_root: &Path, 9300 contract_root: &Path, 9301 contract_version: &str, 9302 release_policy_override: Option<PathBuf>, 9303 ) -> Result<(), String> { 9304 validate_release_publish_policy_with_override_and_control( 9305 workspace_root, 9306 contract_root, 9307 contract_version, 9308 release_policy_override, 9309 true, 9310 false, 9311 ) 9312 } 9313 9314 fn validate_release_publish_policy_with_override_and_control( 9315 workspace_root: &Path, 9316 _contract_root: &Path, 9317 contract_version: &str, 9318 release_policy_override: Option<PathBuf>, 9319 require_publication_control: bool, 9320 require_v1_policy: bool, 9321 ) -> Result<(), String> { 9322 let release = load_release_contract_with_override( 9323 workspace_root, 9324 contract_version, 9325 release_policy_override, 9326 )?; 9327 if release.release.version.trim().is_empty() { 9328 return Err("release.version must not be empty".to_string()); 9329 } 9330 if release.release.version != contract_version { 9331 return Err(format!( 9332 "release.version {} must match contract version {}", 9333 release.release.version, contract_version 9334 )); 9335 } 9336 9337 let workspace_packages = workspace_package_names(workspace_root)? 9338 .into_iter() 9339 .collect::<BTreeSet<_>>(); 9340 let publish_configs = workspace_package_publish_configs(workspace_root) 9341 .expect("workspace publish configs are stable"); 9342 if validate_v1_release_policy( 9343 workspace_root, 9344 &release, 9345 &workspace_packages, 9346 &publish_configs, 9347 require_v1_policy, 9348 )? 9349 .is_some() 9350 { 9351 return Ok(()); 9352 } 9353 let uses_classification = release.uses_classification(); 9354 let public_field = if uses_classification { 9355 "classification.public" 9356 } else { 9357 "publish.crates" 9358 }; 9359 let internal_field = if uses_classification { 9360 "classification.internal" 9361 } else { 9362 "internal.crates" 9363 }; 9364 9365 let public_set = collect_unique_set(&release.public_crates(), public_field)?; 9366 let internal_set = collect_unique_set(&release.internal_crates(), internal_field)?; 9367 let deferred_set = collect_unique_set(&release.deferred_crates(), "classification.deferred")?; 9368 let retired_set = collect_unique_set(&release.retired_crates(), "classification.retired")?; 9369 let yank_only_set = 9370 collect_unique_set(&release.yank_only_crates(), "classification.yank_only")?; 9371 let publish_order = &release.publish_order.crates; 9372 let publish_order_set = collect_unique_set(publish_order, "publish_order.crates")?; 9373 9374 let class_sets = [ 9375 ("public", &public_set), 9376 ("internal", &internal_set), 9377 ("deferred", &deferred_set), 9378 ("retired", &retired_set), 9379 ("yank-only", &yank_only_set), 9380 ]; 9381 for idx in 0..class_sets.len() { 9382 for other_idx in (idx + 1)..class_sets.len() { 9383 let overlap = class_sets[idx] 9384 .1 9385 .intersection(class_sets[other_idx].1) 9386 .cloned() 9387 .collect::<BTreeSet<_>>(); 9388 if !overlap.is_empty() { 9389 return Err(format!( 9390 "release classification overlap is not allowed between {} and {}: {}", 9391 class_sets[idx].0, 9392 class_sets[other_idx].0, 9393 join_set(&overlap) 9394 )); 9395 } 9396 } 9397 } 9398 9399 let mut combined = public_set.clone(); 9400 combined.extend(internal_set.iter().cloned()); 9401 combined.extend(deferred_set.iter().cloned()); 9402 combined.extend(retired_set.iter().cloned()); 9403 combined.extend(yank_only_set.iter().cloned()); 9404 if combined != workspace_packages { 9405 let missing = workspace_packages 9406 .difference(&combined) 9407 .cloned() 9408 .collect::<BTreeSet<_>>(); 9409 let extra = combined 9410 .difference(&workspace_packages) 9411 .cloned() 9412 .collect::<BTreeSet<_>>(); 9413 return Err(format!( 9414 "release classification sets are missing workspace crates: {}; release classification sets include unknown crates: {}", 9415 join_set(&missing), 9416 join_set(&extra) 9417 )); 9418 } 9419 9420 if publish_order_set != public_set { 9421 let missing = public_set 9422 .difference(&publish_order_set) 9423 .cloned() 9424 .collect::<BTreeSet<_>>(); 9425 let extra = publish_order_set 9426 .difference(&public_set) 9427 .cloned() 9428 .collect::<BTreeSet<_>>(); 9429 return Err(format!( 9430 "publish_order.crates is missing publish crates: {}; publish_order.crates has non-publish crates: {}", 9431 join_set(&missing), 9432 join_set(&extra) 9433 )); 9434 } 9435 9436 let order_index = publish_order 9437 .iter() 9438 .enumerate() 9439 .map(|(idx, name)| (name.clone(), idx)) 9440 .collect::<BTreeMap<_, _>>(); 9441 let dependencies = read_workspace_package_dependencies(workspace_root) 9442 .expect("workspace package manifests were already parsed"); 9443 for crate_name in &public_set { 9444 let crate_deps = &dependencies[crate_name]; 9445 let crate_order = order_index[crate_name]; 9446 for dep in crate_deps { 9447 if !public_set.contains(dep) { 9448 continue; 9449 } 9450 let dep_order = order_index[dep]; 9451 if dep_order >= crate_order { 9452 return Err(format!( 9453 "publish order must place dependency {} before {}", 9454 dep, crate_name 9455 )); 9456 } 9457 } 9458 } 9459 9460 if validate_publication_control(&release, &publish_configs, require_publication_control)? { 9461 return Ok(()); 9462 } 9463 for crate_name in &public_set { 9464 let publish = publish_configs[crate_name].as_ref(); 9465 if !publish_config_is_public(publish) { 9466 return Err(format!( 9467 "public crate {} must set publish = [\"crates-io\"]", 9468 crate_name 9469 )); 9470 } 9471 } 9472 for crate_name in internal_set 9473 .iter() 9474 .chain(deferred_set.iter()) 9475 .chain(retired_set.iter()) 9476 .chain(yank_only_set.iter()) 9477 { 9478 let publish = publish_configs[crate_name].as_ref(); 9479 if !publish_config_is_non_public(publish) { 9480 return Err(format!( 9481 "non-public crate {} must set publish = false", 9482 crate_name 9483 )); 9484 } 9485 } 9486 9487 Ok(()) 9488 } 9489 9490 pub fn load_contract_bundle(workspace_root: &Path) -> Result<ContractBundle, String> { 9491 reject_legacy_contract_roots(workspace_root)?; 9492 let root = contract_root(workspace_root); 9493 let manifest = parse_toml::<ContractManifest>(&root.join("manifest.toml"))?; 9494 let version = parse_toml::<VersionPolicy>(&root.join("version.toml"))?; 9495 let replica = 9496 parse_toml::<ReplicaContractManifest>(&workspace_root.join(REPLICA_CONTRACT_RELATIVE))?; 9497 let operations_manifest = 9498 parse_toml::<OperationsContractManifest>(&root.join("operations.toml"))?; 9499 Ok(ContractBundle { 9500 root, 9501 manifest, 9502 version, 9503 replica, 9504 operations_manifest, 9505 }) 9506 } 9507 9508 fn reject_legacy_contract_roots(workspace_root: &Path) -> Result<(), String> { 9509 for relative in ["spec", "policy"] { 9510 let legacy_root = workspace_root.join(relative); 9511 if legacy_root.exists() { 9512 return Err(format!( 9513 "legacy contract root {} is forbidden; use contracts/", 9514 legacy_root.display() 9515 )); 9516 } 9517 } 9518 Ok(()) 9519 } 9520 9521 pub fn validate_contract_bundle(bundle: &ContractBundle) -> Result<(), String> { 9522 validate_contract_bundle_with_release_policy_override(bundle, None) 9523 } 9524 9525 #[cfg(test)] 9526 mod tests { 9527 use super::*; 9528 use std::collections::BTreeSet; 9529 use std::fs; 9530 use std::path::{Path, PathBuf}; 9531 use std::time::{SystemTime, UNIX_EPOCH}; 9532 9533 const SYNTHETIC_CONFORMANCE_VECTOR: &str = r#"{ 9534 "suite": "synthetic", 9535 "contract_version": "1.0.0", 9536 "vectors": [ 9537 { 9538 "id": "synthetic_vector_001", 9539 "kind": "synthetic.operation", 9540 "input": {}, 9541 "expected": {} 9542 } 9543 ] 9544 } 9545 "#; 9546 9547 fn workspace_root() -> PathBuf { 9548 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 9549 manifest_dir 9550 .join("../..") 9551 .canonicalize() 9552 .expect("canonical workspace root") 9553 } 9554 9555 #[test] 9556 fn sqlite_runtime_policy_matches_the_exact_migration_inventory() { 9557 validate_sqlite_runtime_contract(&workspace_root()) 9558 .expect("current SQLx and SQLite migration policy must validate"); 9559 } 9560 9561 #[test] 9562 fn sqlite_dependency_inventory_uses_the_selected_package_not_its_alias() { 9563 let manifest = toml::from_str::<toml::Value>( 9564 r#"[dependencies] 9565 database = { package = "rusqlite", version = "1" } 9566 sqlx = { version = "1" } 9567 "#, 9568 ) 9569 .expect("dependency fixture"); 9570 let dependencies = manifest 9571 .get("dependencies") 9572 .and_then(toml::Value::as_table) 9573 .expect("dependency table"); 9574 let mut found = BTreeSet::new(); 9575 collect_matching_dependencies( 9576 "fixture", 9577 dependencies, 9578 &|dependency| dependency == "rusqlite", 9579 &mut found, 9580 ); 9581 assert_eq!(found, BTreeSet::from(["fixture:rusqlite".to_string()])); 9582 } 9583 9584 fn validate_generic_contract_bundle(bundle: &ContractBundle) -> Result<(), String> { 9585 validate_contract_bundle_with_release_policy_override_and_profile( 9586 bundle, 9587 None, 9588 OperationAuthorityProfile::Generic, 9589 ) 9590 } 9591 9592 fn validate_generic_release_preflight(workspace_root: &Path) -> Result<(), String> { 9593 validate_release_preflight_with_override_and_profile( 9594 workspace_root, 9595 None, 9596 OperationAuthorityProfile::Generic, 9597 ) 9598 } 9599 9600 fn current_post_authority() -> (OperationsContractManifest, ConformanceVectorFile) { 9601 let root = workspace_root(); 9602 let manifest = 9603 parse_toml::<OperationsContractManifest>(&root.join("contracts/operations.toml")) 9604 .expect("current operations manifest"); 9605 let vector = 9606 parse_json::<ConformanceVectorFile>(&root.join(POST_CONFORMANCE_VECTOR_RELATIVE)) 9607 .expect("current post conformance vector"); 9608 (manifest, vector) 9609 } 9610 9611 fn current_admission_authority() -> (OperationsContractManifest, ConformanceVectorFile) { 9612 let root = workspace_root(); 9613 let manifest = 9614 parse_toml::<OperationsContractManifest>(&root.join("contracts/operations.toml")) 9615 .expect("current operations manifest"); 9616 let vector = parse_json::<ConformanceVectorFile>( 9617 &root.join(admission_authority::ADMISSION_CONFORMANCE_VECTOR_RELATIVE), 9618 ) 9619 .expect("current verified admission conformance vector"); 9620 (manifest, vector) 9621 } 9622 9623 fn current_comment_authority() -> (OperationsContractManifest, ConformanceVectorFile) { 9624 let root = workspace_root(); 9625 let manifest = 9626 parse_toml::<OperationsContractManifest>(&root.join("contracts/operations.toml")) 9627 .expect("current operations manifest"); 9628 let vector = 9629 parse_json::<ConformanceVectorFile>(&root.join(COMMENT_CONFORMANCE_VECTOR_RELATIVE)) 9630 .expect("current Comment conformance vector"); 9631 (manifest, vector) 9632 } 9633 9634 fn current_deletion_authority() -> ( 9635 OperationsContractManifest, 9636 ConformanceVectorFile, 9637 ConformanceVectorFile, 9638 ) { 9639 let root = workspace_root(); 9640 let manifest = 9641 parse_toml::<OperationsContractManifest>(&root.join("contracts/operations.toml")) 9642 .expect("current operations manifest"); 9643 let request_vector = 9644 parse_json::<ConformanceVectorFile>(&root.join(DELETION_CONFORMANCE_VECTOR_RELATIVE)) 9645 .expect("current deletion conformance vector"); 9646 let suppression_vector = parse_json::<ConformanceVectorFile>( 9647 &root.join(DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE), 9648 ) 9649 .expect("current deletion suppression conformance vector"); 9650 (manifest, request_vector, suppression_vector) 9651 } 9652 9653 fn current_food_availability_authority() -> (OperationsContractManifest, ConformanceVectorFile) 9654 { 9655 let root = workspace_root(); 9656 let manifest = 9657 parse_toml::<OperationsContractManifest>(&root.join("contracts/operations.toml")) 9658 .expect("current operations manifest"); 9659 let vector = parse_json::<ConformanceVectorFile>( 9660 &root.join(FOOD_AVAILABILITY_CONFORMANCE_VECTOR_RELATIVE), 9661 ) 9662 .expect("current FoodAvailability conformance vector"); 9663 (manifest, vector) 9664 } 9665 9666 fn current_knowledge_manifest_authority() -> (String, ConformanceVectorFile) { 9667 let root = workspace_root(); 9668 let manifest = fs::read_to_string(root.join(KNOWLEDGE_MANIFEST_RELATIVE)) 9669 .expect("current knowledge manifest"); 9670 let vector = 9671 parse_json::<ConformanceVectorFile>(&root.join(KNOWLEDGE_MANIFEST_AND_DECODE_RELATIVE)) 9672 .expect("current knowledge manifest conformance vector"); 9673 (manifest, vector) 9674 } 9675 9676 fn temp_root(prefix: &str) -> PathBuf { 9677 let nanos = SystemTime::now() 9678 .duration_since(UNIX_EPOCH) 9679 .expect("clock") 9680 .as_nanos(); 9681 let root = std::env::temp_dir().join(format!("radroots_xtask_{prefix}_{nanos}")); 9682 fs::create_dir_all(&root).expect("create temp root"); 9683 root 9684 } 9685 9686 fn write_file(path: &Path, content: &str) { 9687 let _ = fs::create_dir_all(path.parent().unwrap_or(Path::new(""))); 9688 fs::write(path, content).expect("write file"); 9689 } 9690 9691 fn required_thresholds() -> CoverageThresholds { 9692 CoverageThresholds { 9693 fail_under_exec_lines: COVERAGE_REQUIRED_THRESHOLD, 9694 fail_under_functions: COVERAGE_REQUIRED_THRESHOLD, 9695 fail_under_regions: COVERAGE_REQUIRED_THRESHOLD, 9696 fail_under_branches: COVERAGE_REQUIRED_THRESHOLD, 9697 require_branches: true, 9698 } 9699 } 9700 9701 fn coverage_thresholds(value: f64, require_branches: bool) -> CoverageThresholds { 9702 CoverageThresholds { 9703 fail_under_exec_lines: value, 9704 fail_under_functions: value, 9705 fail_under_regions: value, 9706 fail_under_branches: value, 9707 require_branches, 9708 } 9709 } 9710 9711 struct TestCoverageRefreshRow<'a> { 9712 crate_name: &'a str, 9713 status: &'a str, 9714 thresholds: CoverageThresholds, 9715 exec: f64, 9716 func: f64, 9717 branch: Option<f64>, 9718 region: f64, 9719 report_pass: bool, 9720 } 9721 9722 fn passing_coverage_row(crate_name: &str) -> TestCoverageRefreshRow<'_> { 9723 TestCoverageRefreshRow { 9724 crate_name, 9725 status: "pass", 9726 thresholds: coverage_thresholds(COVERAGE_REQUIRED_THRESHOLD, true), 9727 exec: 100.0, 9728 func: 100.0, 9729 branch: Some(100.0), 9730 region: 100.0, 9731 report_pass: true, 9732 } 9733 } 9734 9735 fn coverage_refresh_branch_value(branch: Option<f64>) -> String { 9736 branch 9737 .map(|value| value.to_string()) 9738 .unwrap_or_else(|| "unavailable".to_string()) 9739 } 9740 9741 fn write_test_coverage_gate_report(root: &Path, row: &TestCoverageRefreshRow<'_>) -> String { 9742 let report_relative = format!("target/coverage/{}/gate-report.json", row.crate_name); 9743 let report_path = root.join(&report_relative); 9744 let fail_reasons = if row.report_pass { 9745 Vec::<&str>::new() 9746 } else { 9747 vec!["policy gate failed"] 9748 }; 9749 let report = serde_json::json!({ 9750 "scope": row.crate_name, 9751 "thresholds": { 9752 "executable_lines": row.thresholds.fail_under_exec_lines, 9753 "functions": row.thresholds.fail_under_functions, 9754 "regions": row.thresholds.fail_under_regions, 9755 "branches": row.thresholds.fail_under_branches, 9756 "branches_required": row.thresholds.require_branches 9757 }, 9758 "measured": { 9759 "executable_lines_percent": row.exec, 9760 "executable_lines_source": "da", 9761 "functions_percent": row.func, 9762 "branches_percent": row.branch, 9763 "branches_available": row.branch.is_some(), 9764 "summary_lines_percent": row.exec, 9765 "summary_regions_percent": row.region 9766 }, 9767 "counts": { 9768 "executable_lines": { 9769 "covered": 1, 9770 "total": 1 9771 }, 9772 "branches": { 9773 "covered": if row.branch.is_some() { 1 } else { 0 }, 9774 "total": if row.branch.is_some() { 1 } else { 0 } 9775 } 9776 }, 9777 "result": { 9778 "pass": row.report_pass, 9779 "fail_reasons": fail_reasons 9780 } 9781 }); 9782 let json = 9783 serde_json::to_string_pretty(&report).expect("serialize test coverage gate report"); 9784 write_file(&report_path, &format!("{json}\n")); 9785 report_relative 9786 } 9787 9788 fn write_test_coverage_refresh(root: &Path, rows: &[TestCoverageRefreshRow<'_>]) { 9789 let mut refresh_rows = String::from("crate\tstatus\texec\tfunc\tbranch\tregion\treport\n"); 9790 for row in rows { 9791 let report_relative = write_test_coverage_gate_report(root, row); 9792 refresh_rows.push_str(&format!( 9793 "{}\t{}\t{}\t{}\t{}\t{}\t{}\n", 9794 row.crate_name, 9795 row.status, 9796 row.exec, 9797 row.func, 9798 coverage_refresh_branch_value(row.branch), 9799 row.region, 9800 report_relative 9801 )); 9802 } 9803 write_file( 9804 &root 9805 .join("target") 9806 .join("coverage") 9807 .join("coverage-refresh.tsv"), 9808 &refresh_rows, 9809 ); 9810 } 9811 9812 fn create_synthetic_workspace(prefix: &str) -> PathBuf { 9813 let root = temp_root(prefix); 9814 write_file( 9815 &root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"), 9816 r#"spec_id = "radroots.crates.release.v1" 9817 package_count = 2 9818 9819 [repositories.lib] 9820 version = "1.0.0" 9821 packages = ["radroots_a", "radroots_b"] 9822 9823 [repositories.sdk] 9824 version = "0.1.0" 9825 packages = [] 9826 9827 [[package]] 9828 name = "radroots_a" 9829 9830 [[package]] 9831 name = "radroots_b" 9832 "#, 9833 ); 9834 write_file( 9835 &root.join("Cargo.toml"), 9836 r#"[workspace] 9837 members = ["crates/a", "crates/b"] 9838 resolver = "2" 9839 9840 [workspace.package] 9841 version = "1.0.0" 9842 9843 [workspace.dependencies] 9844 radroots_a = { path = "crates/a", version = "=1.0.0" } 9845 radroots_b = { path = "crates/b", version = "=1.0.0" } 9846 "#, 9847 ); 9848 write_file( 9849 &root.join("crates").join("a").join("Cargo.toml"), 9850 r#"[package] 9851 name = "radroots_a" 9852 publish = ["crates-io"] 9853 version = "1.0.0" 9854 edition = "2024" 9855 authors = ["Radroots Test"] 9856 rust-version = "1.97" 9857 license = "MIT OR Apache-2.0" 9858 description = "crate a" 9859 repository = "https://example.com/a" 9860 homepage = "https://example.com/a" 9861 documentation = "https://docs.rs/radroots_a" 9862 readme = "README.md" 9863 keywords = ["radroots"] 9864 categories = ["data-structures"] 9865 include = ["src/**", "tests/**", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 9866 9867 [package.metadata.docs.rs] 9868 features = [] 9869 "#, 9870 ); 9871 write_file(&root.join("LICENSE-APACHE"), "Apache license\n"); 9872 write_file(&root.join("LICENSE-MIT"), "MIT license\n"); 9873 for relative in ["README.md", "LICENSE-APACHE", "LICENSE-MIT"] { 9874 let contents = if relative == "README.md" { 9875 "# radroots_a\n".to_owned() 9876 } else { 9877 fs::read_to_string(root.join(relative)).expect("read synthetic package metadata") 9878 }; 9879 write_file(&root.join("crates").join("a").join(relative), &contents); 9880 } 9881 write_file( 9882 &root.join("crates").join("b").join("Cargo.toml"), 9883 r#"[package] 9884 name = "radroots_b" 9885 version = "1.0.0" 9886 edition = "2024" 9887 publish = false 9888 9889 [features] 9890 default = ["std"] 9891 std = [] 9892 legacy-ingest = ["std"] 9893 "#, 9894 ); 9895 write_file( 9896 &root.join("crates").join("b").join("src").join("lib.rs"), 9897 r#"#[cfg(feature = "legacy-ingest")] 9898 pub mod ingest; 9899 9900 #[cfg(feature = "legacy-ingest")] 9901 pub use ingest::{radroots_replica_ingest_event, RadrootsReplicaIngestOutcome}; 9902 "#, 9903 ); 9904 write_file( 9905 &root.join("crates").join("b").join("src").join("types.rs"), 9906 r#"use serde::{Deserialize, Serialize}; 9907 9908 pub const RADROOTS_REPLICA_TRANSFER_VERSION: u32 = 2; 9909 9910 #[derive(Clone, Debug, Serialize, Deserialize)] 9911 #[serde(deny_unknown_fields)] 9912 pub struct RadrootsReplicaFarmSelector; 9913 9914 #[derive(Clone, Debug, Serialize, Deserialize)] 9915 #[serde(deny_unknown_fields)] 9916 pub struct RadrootsReplicaSyncOptions; 9917 9918 #[derive(Clone, Debug, Serialize, Deserialize)] 9919 #[serde(deny_unknown_fields)] 9920 pub struct RadrootsReplicaSyncRequest; 9921 "#, 9922 ); 9923 write_file( 9924 &root.join("crates").join("b").join("src").join("emit.rs"), 9925 r#"pub fn radroots_replica_sync_all_with_options() {} 9926 9927 #[cfg(test)] 9928 mod tests {} 9929 "#, 9930 ); 9931 write_file( 9932 &root.join("Cargo.lock"), 9933 r#"version = 4 9934 9935 [[package]] 9936 name = "radroots_a" 9937 version = "1.0.0" 9938 9939 [[package]] 9940 name = "radroots_b" 9941 version = "1.0.0" 9942 "#, 9943 ); 9944 write_file( 9945 &root.join("crates").join("core").join("src").join("unit.rs"), 9946 r#"pub enum UnitDimension { 9947 Count, 9948 Mass, 9949 Volume, 9950 } 9951 "#, 9952 ); 9953 9954 write_file( 9955 &root.join("contracts").join("manifest.toml"), 9956 r#"[contract] 9957 name = "radroots_contract" 9958 version = "1.0.0" 9959 source = "synthetic" 9960 9961 [surface] 9962 model_crates = ["radroots_a"] 9963 algorithm_crates = ["radroots_b"] 9964 9965 [surface.internal_replica_crates] 9966 schema = "radroots_a" 9967 storage = "radroots_b" 9968 sync = "radroots_b" 9969 9970 [policy] 9971 exclude_internal_workspace_crates = true 9972 require_reproducible_exports = true 9973 require_conformance_vectors = true 9974 9975 [policy.replica] 9976 forbid_legacy_alias_identifiers = true 9977 require_transport_agnostic_sync_contract = true 9978 require_deterministic_emit_ingest = true 9979 "#, 9980 ); 9981 write_file( 9982 &root.join("contracts").join("version.toml"), 9983 r#"[contract] 9984 version = "1.0.0" 9985 stability = "alpha" 9986 9987 [semver] 9988 major_on = ["breaking"] 9989 minor_on = ["feature"] 9990 patch_on = ["fix"] 9991 9992 [release_integrity] 9993 requires_conformance_pass = true 9994 requires_contract_manifest_diff = true 9995 requires_release_notes = true 9996 "#, 9997 ); 9998 write_file( 9999 &root.join("contracts").join("replica.toml"), 10000 r#"schema_version = 1 10001 10002 [contract] 10003 name = "radroots_replica_contract" 10004 version = "1.0.0" 10005 purpose = "synthetic deterministic replica sync" 10006 10007 [crate_family] 10008 schema = "radroots_a" 10009 storage = "radroots_b" 10010 sync = "radroots_b" 10011 10012 [policy] 10013 transport_agnostic_sync_core = true 10014 deterministic_emit_and_ingest = true 10015 forbid_legacy_alias_identifiers = true 10016 profile_event_emission = "excluded" 10017 unknown_sync_request_fields = "reject" 10018 classified_listing_signature_verification = "required_before_state" 10019 classified_listing_head_selection = "raw_before_profile" 10020 classified_listing_operational_projection = "operational_partition_only" 10021 classified_listing_excluded_or_rejected_head = "remove_projection_and_advance" 10022 classified_listing_head_only_ingest = "reject_require_profile_aware" 10023 legacy_bare_envelope_ingest = "explicit_non_default_feature_only" 10024 legacy_ingest_feature = "legacy-ingest" 10025 phase_1_ingest_replacement = "none" 10026 future_product_ingest_input = "store_produced_verified_valid_visible_admission" 10027 10028 [transfer] 10029 version = 2 10030 source = "crates/b/src/types.rs" 10031 constant = "RADROOTS_REPLICA_TRANSFER_VERSION" 10032 "#, 10033 ); 10034 write_file( 10035 &root.join("contracts").join("coverage.toml"), 10036 r#"[gate] 10037 fail_under_exec_lines = 90.0 10038 fail_under_functions = 90.0 10039 fail_under_regions = 90.0 10040 fail_under_branches = 90.0 10041 require_branches = true 10042 10043 [required] 10044 crates = ["radroots_a", "radroots_b"] 10045 "#, 10046 ); 10047 write_file( 10048 &root.join(CHANGELOG_RELATIVE), 10049 "# Changelog\n\n## [1.0.0]\n\n- Synthetic breaking release.\n", 10050 ); 10051 for (canonical_relative, mirror_relative) in CONFORMANCE_VECTOR_MIRRORS { 10052 write_file(&root.join(canonical_relative), SYNTHETIC_CONFORMANCE_VECTOR); 10053 write_file(&root.join(mirror_relative), SYNTHETIC_CONFORMANCE_VECTOR); 10054 } 10055 write_file( 10056 &root.join(RELEASES_ROOT_RELATIVE).join("1.0.0.toml"), 10057 r#"schema_version = 1 10058 10059 [release] 10060 version = "1.0.0" 10061 previous_version = "0.1.0-alpha.2" 10062 contract_base_version = "1.0.0" 10063 status = "unreleased" 10064 10065 [artifacts] 10066 changelog = "CHANGELOG.md" 10067 manifest = "contracts/manifest.toml" 10068 operations = "contracts/operations.toml" 10069 replica = "contracts/replica.toml" 10070 conformance = "contracts/conformance" 10071 publish_policy = "contracts/releases/publish_policy.toml" 10072 10073 [[changes]] 10074 id = "synthetic-major-release" 10075 classification = "breaking" 10076 semver_impacts = ["breaking"] 10077 summary = "Exercise synthetic major release governance." 10078 "#, 10079 ); 10080 write_file( 10081 &root_release_policy_path(&root), 10082 r#"[release] 10083 version = "1.0.0" 10084 10085 [publish] 10086 crates = ["radroots_a"] 10087 10088 [internal] 10089 crates = ["radroots_b"] 10090 10091 [publish_order] 10092 crates = ["radroots_a"] 10093 "#, 10094 ); 10095 write_test_coverage_refresh( 10096 &root, 10097 &[ 10098 passing_coverage_row("radroots_a"), 10099 passing_coverage_row("radroots_b"), 10100 ], 10101 ); 10102 add_operation_contract_files(&root); 10103 root 10104 } 10105 10106 fn add_operation_contract_files(root: &Path) { 10107 write_file( 10108 &root.join("contracts").join("operations.toml"), 10109 r#"[contract] 10110 name = "radroots_contract" 10111 version = "1.0.0" 10112 source = "synthetic" 10113 10114 [public] 10115 domains = ["profile", "farm", "operational_listing", "trade"] 10116 10117 [shared_types] 10118 public = [ 10119 "Nip01EventWireParts", 10120 "GenericEventDraft", 10121 "SignedEvent", 10122 "EventEnvelope", 10123 "EventRef", 10124 "EventPtr", 10125 "ClassifiedListingAddress", 10126 "AuthoredProfile", 10127 "RadrootsInboundProfileMetadata", 10128 "Farm", 10129 "OperationalListing", 10130 ] 10131 10132 [errors] 10133 classes = ["encode_error", "parse_error", "validation_error", "address_error"] 10134 10135 [implementation_provenance] 10136 model_crates = ["radroots_a"] 10137 algorithm_crates = ["radroots_b"] 10138 10139 [operations.profile_build_authored_draft] 10140 domain = "profile" 10141 id = "profile.build_authored_draft" 10142 stability = "beta" 10143 inputs = ["AuthoredProfile"] 10144 outputs = ["Nip01EventWireParts"] 10145 error_class = "encode_error" 10146 deterministic = true 10147 signing = "native" 10148 transport = "native" 10149 10150 [operations.profile_build_authored_draft.implementation] 10151 rust_modules = ["crates/core/src/unit.rs"] 10152 rust_types = ["radroots_event::profile::AuthoredProfile"] 10153 10154 [operations.profile_build_authored_draft.conformance] 10155 vector = "contracts/conformance/vectors/profile/metadata.v1.json" 10156 10157 [operations.operational_listing_build_draft] 10158 domain = "operational_listing" 10159 id = "operational_listing.build_draft" 10160 stability = "beta" 10161 inputs = ["OperationalListing"] 10162 outputs = ["Nip01EventWireParts"] 10163 error_class = "encode_error" 10164 deterministic = true 10165 signing = "native" 10166 transport = "native" 10167 10168 [operations.operational_listing_build_draft.implementation] 10169 rust_modules = ["crates/core/src/unit.rs"] 10170 rust_types = ["radroots_event::listing::operational::OperationalListing"] 10171 10172 [operations.operational_listing_build_draft.conformance] 10173 vector = "contracts/conformance/vectors/operational_listing/build_draft.v1.json" 10174 "#, 10175 ); 10176 write_file( 10177 &root 10178 .join("contracts") 10179 .join("conformance") 10180 .join("schema") 10181 .join("vector.schema.json"), 10182 r#"{ 10183 "$schema": "http://json-schema.org/draft-07/schema#", 10184 "$id": "https://radroots.org/core/conformance/vector.schema.json", 10185 "title": "radroots core conformance vector", 10186 "type": "object", 10187 "required": ["suite", "contract_version", "vectors"], 10188 "properties": { 10189 "suite": { 10190 "type": "string", 10191 "minLength": 1 10192 }, 10193 "contract_version": { 10194 "type": "string", 10195 "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" 10196 }, 10197 "vectors": { 10198 "type": "array", 10199 "items": { 10200 "type": "object", 10201 "required": ["id", "kind", "input"], 10202 "properties": { 10203 "id": { 10204 "type": "string", 10205 "minLength": 1 10206 }, 10207 "kind": { 10208 "type": "string", 10209 "minLength": 1 10210 }, 10211 "input": {}, 10212 "expected": {}, 10213 "expected_error_contains": { 10214 "type": "string", 10215 "minLength": 1 10216 } 10217 }, 10218 "oneOf": [ 10219 { 10220 "required": ["expected"], 10221 "not": {"required": ["expected_error_contains"]} 10222 }, 10223 { 10224 "required": ["expected_error_contains"], 10225 "not": {"required": ["expected"]} 10226 } 10227 ], 10228 "additionalProperties": false 10229 } 10230 } 10231 }, 10232 "additionalProperties": false 10233 } 10234 "#, 10235 ); 10236 write_file( 10237 &root 10238 .join("contracts") 10239 .join("conformance") 10240 .join("vectors") 10241 .join("profile") 10242 .join("metadata.v1.json"), 10243 SYNTHETIC_CONFORMANCE_VECTOR, 10244 ); 10245 let operational_listing_vector = r#"{ 10246 "suite": "operational_listing", 10247 "contract_version": "1.0.0", 10248 "vectors": [ 10249 { 10250 "id": "operational_listing_build_draft_minimal_001", 10251 "kind": "operational_listing.build_draft", 10252 "input": {}, 10253 "expected": {} 10254 } 10255 ] 10256 } 10257 "#; 10258 write_file( 10259 &root 10260 .join("contracts") 10261 .join("conformance") 10262 .join("vectors") 10263 .join("operational_listing") 10264 .join("build_draft.v1.json"), 10265 operational_listing_vector, 10266 ); 10267 write_file( 10268 &root 10269 .join("crates") 10270 .join("event_codec") 10271 .join("tests") 10272 .join("fixtures") 10273 .join("operational_listing_build_draft.v1.json"), 10274 operational_listing_vector, 10275 ); 10276 } 10277 10278 fn write_root_release_policy(root: &Path, raw: &str) { 10279 write_file(&root_release_policy_path(root), raw); 10280 } 10281 10282 fn configure_root_release_policy_workspace(root: &Path) { 10283 write_file( 10284 &root.join("Cargo.toml"), 10285 r#"[workspace] 10286 members = ["crates/a", "crates/b", "crates/c", "crates/d", "crates/e"] 10287 resolver = "2" 10288 10289 [workspace.package] 10290 version = "1.0.0" 10291 10292 [workspace.dependencies] 10293 radroots_a = { path = "crates/a", version = "=1.0.0" } 10294 radroots_b = { path = "crates/b", version = "=1.0.0" } 10295 radroots_c = { path = "crates/c", version = "=1.0.0" } 10296 radroots_d = { path = "crates/d", version = "=1.0.0" } 10297 radroots_e = { path = "crates/e", version = "=1.0.0" } 10298 "#, 10299 ); 10300 for crate_name in ["c", "d", "e"] { 10301 write_file( 10302 &root.join("crates").join(crate_name).join("Cargo.toml"), 10303 &format!( 10304 r#"[package] 10305 name = "radroots_{crate_name}" 10306 version = "1.0.0" 10307 edition = "2024" 10308 publish = false 10309 "# 10310 ), 10311 ); 10312 } 10313 write_file( 10314 &root.join("Cargo.lock"), 10315 r#"version = 4 10316 10317 [[package]] 10318 name = "radroots_a" 10319 version = "1.0.0" 10320 10321 [[package]] 10322 name = "radroots_b" 10323 version = "1.0.0" 10324 10325 [[package]] 10326 name = "radroots_c" 10327 version = "1.0.0" 10328 10329 [[package]] 10330 name = "radroots_d" 10331 version = "1.0.0" 10332 10333 [[package]] 10334 name = "radroots_e" 10335 version = "1.0.0" 10336 "#, 10337 ); 10338 write_file( 10339 &root.join("contracts").join("coverage.toml"), 10340 r#"[gate] 10341 fail_under_exec_lines = 90.0 10342 fail_under_functions = 90.0 10343 fail_under_regions = 90.0 10344 fail_under_branches = 90.0 10345 require_branches = true 10346 10347 [required] 10348 crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"] 10349 "#, 10350 ); 10351 write_test_coverage_refresh( 10352 root, 10353 &[ 10354 passing_coverage_row("radroots_a"), 10355 passing_coverage_row("radroots_b"), 10356 passing_coverage_row("radroots_c"), 10357 passing_coverage_row("radroots_d"), 10358 passing_coverage_row("radroots_e"), 10359 ], 10360 ); 10361 let _ = fs::remove_file(root_release_policy_path(root)); 10362 } 10363 10364 #[test] 10365 fn validate_current_contract_bundle() { 10366 let root = workspace_root(); 10367 let bundle = load_contract_bundle(&root).expect("load contract"); 10368 validate_contract_bundle(&bundle).expect("validate contract"); 10369 } 10370 10371 #[test] 10372 fn knowledge_manifest_vector_authority_rejects_registry_drift() { 10373 let (manifest, vector) = current_knowledge_manifest_authority(); 10374 validate_knowledge_manifest_vector_semantics(&manifest, &vector) 10375 .expect("current knowledge manifest vector authority"); 10376 10377 let (manifest, mut vector) = current_knowledge_manifest_authority(); 10378 let case = vector 10379 .vectors 10380 .iter_mut() 10381 .find(|entry| entry.id == "knowledge_manifest_fields_valid_001") 10382 .expect("knowledge manifest case"); 10383 case.input 10384 .as_object_mut() 10385 .expect("knowledge manifest input") 10386 .insert( 10387 "registry".to_string(), 10388 Value::String("radroots_event_contract_registry_v1".to_string()), 10389 ); 10390 let error = validate_knowledge_manifest_vector_semantics(&manifest, &vector) 10391 .expect_err("stale registry marker must fail"); 10392 assert!(error.contains("registry marker drift"), "{error}"); 10393 10394 let (manifest, mut vector) = current_knowledge_manifest_authority(); 10395 let case = vector 10396 .vectors 10397 .iter_mut() 10398 .find(|entry| entry.id == "knowledge_manifest_fields_valid_001") 10399 .expect("knowledge manifest case"); 10400 case.expected 10401 .as_mut() 10402 .expect("knowledge manifest expected output") 10403 .as_object_mut() 10404 .expect("knowledge manifest expected output object") 10405 .insert("registry_version".to_string(), Value::from(1_u64)); 10406 let error = validate_knowledge_manifest_vector_semantics(&manifest, &vector) 10407 .expect_err("stale expected registry version must fail"); 10408 assert!(error.contains("expected registry_version drift"), "{error}"); 10409 } 10410 10411 #[test] 10412 fn knowledge_manifest_artifacts_are_atomic_fresh_and_shape_checked() { 10413 let root = temp_root("knowledge_manifest_artifacts"); 10414 let generated = 10415 write_knowledge_contract_manifest_artifacts(&root).expect("write manifest artifacts"); 10416 assert_eq!( 10417 generated, 10418 expected_knowledge_contract_manifest_json().expect("expected manifest") 10419 ); 10420 assert_eq!( 10421 validate_knowledge_contract_manifest_artifacts(&root) 10422 .expect("fresh manifest artifacts"), 10423 generated 10424 ); 10425 10426 let manifest_path = root.join(KNOWLEDGE_MANIFEST_RELATIVE); 10427 let mut extra_lf = fs::read_to_string(&manifest_path).expect("read manifest"); 10428 extra_lf.push('\n'); 10429 fs::write(&manifest_path, extra_lf).expect("write noncanonical manifest"); 10430 let error = validate_knowledge_contract_manifest_artifacts(&root) 10431 .expect_err("noncanonical manifest must fail"); 10432 assert!(error.contains("exactly one LF"), "{error}"); 10433 10434 write_knowledge_contract_manifest_artifacts(&root).expect("restore manifest artifacts"); 10435 let mut value: Value = 10436 serde_json::from_slice(&fs::read(&manifest_path).expect("read restored manifest")) 10437 .expect("parse restored manifest"); 10438 value["contract_count"] = Value::from(0_u64); 10439 let mut mismatched = serde_json::to_string_pretty(&value).expect("serialize mismatch"); 10440 mismatched.push('\n'); 10441 fs::write(&manifest_path, mismatched).expect("write count mismatch"); 10442 let error = validate_knowledge_contract_manifest_artifacts(&root) 10443 .expect_err("count mismatch must fail"); 10444 assert!(error.contains("contract_count"), "{error}"); 10445 10446 let _ = fs::remove_dir_all(root); 10447 } 10448 10449 #[test] 10450 fn strict_post_release_record_governs_public_boundary_breaks() { 10451 let root = workspace_root(); 10452 let bundle = load_contract_bundle(&root).expect("load current contract bundle"); 10453 let major_impacts = bundle 10454 .version 10455 .semver 10456 .major_on 10457 .iter() 10458 .map(String::as_str) 10459 .collect::<BTreeSet<_>>(); 10460 for impact in [ 10461 "add_exported_field", 10462 "change_exported_function_signature", 10463 "change_exported_constant_value", 10464 ] { 10465 assert!( 10466 major_impacts.contains(impact), 10467 "version policy must govern {impact} as a major impact" 10468 ); 10469 } 10470 assert!( 10471 bundle 10472 .version 10473 .semver 10474 .minor_on 10475 .iter() 10476 .any(|impact| impact == "add_exported_constant"), 10477 "version policy must govern add_exported_constant as a minor impact" 10478 ); 10479 10480 let release = 10481 parse_toml::<ReleaseRecord>(&root.join("contracts/releases/1.0.0-alpha.1.toml")) 10482 .expect("current release record"); 10483 let change = release 10484 .changes 10485 .iter() 10486 .find(|change| change.id == "strict-kind-one-product-profiles") 10487 .expect("strict kind-one release change"); 10488 let impacts = change 10489 .semver_impacts 10490 .iter() 10491 .map(String::as_str) 10492 .collect::<BTreeSet<_>>(); 10493 for impact in [ 10494 "remove_exported_type", 10495 "add_exported_constant", 10496 "add_exported_field", 10497 "change_exported_function_signature", 10498 "change_exported_enum_variant", 10499 "change_exported_constant_value", 10500 ] { 10501 assert!( 10502 impacts.contains(impact), 10503 "strict kind-one release change must declare {impact}" 10504 ); 10505 } 10506 } 10507 10508 #[test] 10509 fn post_operation_authority_rejects_manifest_and_inventory_drift() { 10510 let (manifest, vector) = current_post_authority(); 10511 validate_post_operation_inventory(&manifest, &vector).expect("current post authority"); 10512 10513 let (mut manifest, vector) = current_post_authority(); 10514 manifest 10515 .operations 10516 .remove("social_update_build_authored_draft"); 10517 let error = validate_post_operation_inventory(&manifest, &vector) 10518 .expect_err("missing post operation must fail"); 10519 assert!(error.contains("post operation authority drift")); 10520 10521 let (mut manifest, vector) = current_post_authority(); 10522 manifest 10523 .operations 10524 .get_mut("social_update_build_authored_draft") 10525 .expect("Update operation") 10526 .id = "social.update.wrong".to_string(); 10527 let error = validate_post_operation_inventory(&manifest, &vector) 10528 .expect_err("wrong post operation ID must fail"); 10529 assert!(error.contains("id drift")); 10530 10531 let (mut manifest, vector) = current_post_authority(); 10532 manifest 10533 .operations 10534 .get_mut("social_update_build_authored_draft") 10535 .expect("Update operation") 10536 .conformance 10537 .vector = "contracts/conformance/vectors/profile/metadata.v1.json".to_string(); 10538 let error = validate_post_operation_inventory(&manifest, &vector) 10539 .expect_err("wrong post vector path must fail"); 10540 assert!(error.contains("conformance.vector drift")); 10541 10542 let (mut manifest, vector) = current_post_authority(); 10543 manifest 10544 .operations 10545 .get_mut("social_update_build_authored_draft") 10546 .expect("Update operation") 10547 .conformance 10548 .case_kinds[0] = "social.ask.build_authored_draft.valid".to_string(); 10549 let error = validate_post_operation_inventory(&manifest, &vector) 10550 .expect_err("wrong post case prefix must fail"); 10551 assert!(error.contains("must start with social.update.build_authored_draft.")); 10552 10553 let (mut manifest, vector) = current_post_authority(); 10554 manifest 10555 .operations 10556 .get_mut("social_update_build_authored_draft") 10557 .expect("Update operation") 10558 .conformance 10559 .case_kinds 10560 .pop(); 10561 let error = validate_post_operation_inventory(&manifest, &vector) 10562 .expect_err("missing post case kind must fail"); 10563 assert!(error.contains("conformance.case_kinds drift")); 10564 10565 let (mut manifest, vector) = current_post_authority(); 10566 let operation = manifest 10567 .operations 10568 .get_mut("social_update_build_authored_draft") 10569 .expect("Update operation"); 10570 operation.conformance.case_kinds[1] = operation.conformance.case_kinds[0].clone(); 10571 let error = validate_post_operation_inventory(&manifest, &vector) 10572 .expect_err("duplicate post case kind must fail"); 10573 assert!(error.contains("duplicate value"), "{error}"); 10574 10575 let (manifest, mut vector) = current_post_authority(); 10576 vector.vectors.push(ConformanceVectorEntry { 10577 id: "unclaimed_post_case".to_string(), 10578 kind: "social.post.unclaimed.valid".to_string(), 10579 input: Value::Object(Default::default()), 10580 expected: Some(Value::Object(Default::default())), 10581 expected_error_contains: None, 10582 }); 10583 let error = validate_post_operation_inventory(&manifest, &vector) 10584 .expect_err("unclaimed post vector kind must fail"); 10585 assert!(error.contains("is not claimed by exactly one operation")); 10586 10587 let (manifest, mut vector) = current_post_authority(); 10588 vector.vectors.remove( 10589 vector 10590 .vectors 10591 .iter() 10592 .position(|entry| entry.kind == "social.post.project_verified_event.valid") 10593 .expect("project valid case"), 10594 ); 10595 let error = validate_post_operation_inventory(&manifest, &vector) 10596 .expect_err("post vector count drift must fail"); 10597 assert!(error.contains("post conformance vector inventory drift")); 10598 } 10599 10600 #[test] 10601 fn verified_admission_authority_rejects_manifest_fixture_and_secret_drift() { 10602 let (manifest, vector) = current_admission_authority(); 10603 admission_authority::validate_admission_operation_inventory(&manifest, &vector) 10604 .expect("current verified admission authority"); 10605 10606 let (mut manifest, vector) = current_admission_authority(); 10607 manifest.operations.remove("event_admit_verified"); 10608 let error = admission_authority::validate_admission_operation_inventory(&manifest, &vector) 10609 .expect_err("missing central admission operation must fail"); 10610 assert!(error.contains("operation authority drift"), "{error}"); 10611 10612 let (mut manifest, vector) = current_admission_authority(); 10613 manifest 10614 .shared_types 10615 .public 10616 .retain(|value| value != "RadrootsEventAdmissionError"); 10617 let error = admission_authority::validate_admission_operation_inventory(&manifest, &vector) 10618 .expect_err("missing central admission public type must fail"); 10619 assert!(error.contains("requires shared public type"), "{error}"); 10620 10621 let (manifest, mut vector) = current_admission_authority(); 10622 vector.vectors[0] 10623 .input 10624 .as_object_mut() 10625 .expect("admission input") 10626 .insert( 10627 "secret_key".to_string(), 10628 Value::String("forbidden".to_string()), 10629 ); 10630 let error = admission_authority::validate_admission_operation_inventory(&manifest, &vector) 10631 .expect_err("fixture secret material must fail exact input inventory"); 10632 assert!(error.contains("field inventory drift"), "{error}"); 10633 10634 let (manifest, mut vector) = current_admission_authority(); 10635 vector.vectors[0].id = "renamed_admission_case".to_string(); 10636 let error = admission_authority::validate_admission_operation_inventory(&manifest, &vector) 10637 .expect_err("renamed admission vector must fail exact inventory"); 10638 assert!(error.contains("unexpected id"), "{error}"); 10639 } 10640 10641 #[test] 10642 fn post_operation_authority_rejects_another_vector_namespace_operation() { 10643 let (mut manifest, vector) = current_post_authority(); 10644 let mut unexpected = manifest 10645 .operations 10646 .remove("social_reaction_build_tags") 10647 .expect("unrelated social operation"); 10648 unexpected.id = "social.update.shadow".to_string(); 10649 manifest 10650 .operations 10651 .insert("social_update_shadow".to_string(), unexpected); 10652 10653 let error = validate_post_operation_inventory(&manifest, &vector) 10654 .expect_err("another-vector post namespace operation must fail"); 10655 assert!(error.contains("post operation authority drift"), "{error}"); 10656 assert!(error.contains("social_update_shadow"), "{error}"); 10657 } 10658 10659 #[test] 10660 fn post_operation_authority_rejects_metadata_drift() { 10661 let (mut manifest, vector) = current_post_authority(); 10662 manifest 10663 .operations 10664 .get_mut("social_update_build_authored_draft") 10665 .expect("Update operation") 10666 .stability = "stable".to_string(); 10667 10668 let error = validate_post_operation_inventory(&manifest, &vector) 10669 .expect_err("post operation metadata drift must fail"); 10670 assert!(error.contains("stability drift"), "{error}"); 10671 } 10672 10673 #[test] 10674 fn post_operation_authority_rejects_required_public_type_removal() { 10675 let (mut manifest, vector) = current_post_authority(); 10676 manifest 10677 .shared_types 10678 .public 10679 .retain(|value| value != "RadrootsPostAdmissionOutcome"); 10680 10681 let error = validate_post_operation_inventory(&manifest, &vector) 10682 .expect_err("required post public type removal must fail"); 10683 assert!( 10684 error.contains( 10685 "post operation authority requires shared public type RadrootsPostAdmissionOutcome" 10686 ), 10687 "{error}" 10688 ); 10689 } 10690 10691 #[test] 10692 fn post_operation_authority_rejects_same_count_vector_id_replacement() { 10693 let (manifest, mut vector) = current_post_authority(); 10694 vector 10695 .vectors 10696 .iter_mut() 10697 .find(|entry| entry.id == "authored_update_wire") 10698 .expect("authored Update case") 10699 .id = "authored_update_wire_replacement".to_string(); 10700 10701 let error = validate_post_operation_inventory(&manifest, &vector) 10702 .expect_err("same-count post vector ID replacement must fail"); 10703 assert!(error.contains("post conformance vector inventory drift")); 10704 } 10705 10706 #[test] 10707 fn post_operation_authority_rejects_vector_id_kind_drift() { 10708 let (manifest, mut vector) = current_post_authority(); 10709 let update_position = vector 10710 .vectors 10711 .iter() 10712 .position(|entry| entry.id == "authored_update_wire") 10713 .expect("authored Update case"); 10714 let ask_position = vector 10715 .vectors 10716 .iter() 10717 .position(|entry| entry.id == "authored_ask_wire") 10718 .expect("authored Ask case"); 10719 let update_kind = vector.vectors[update_position].kind.clone(); 10720 vector.vectors[update_position].kind = vector.vectors[ask_position].kind.clone(); 10721 vector.vectors[ask_position].kind = update_kind; 10722 10723 let error = validate_post_operation_inventory(&manifest, &vector) 10724 .expect_err("post vector ID-to-kind drift must fail"); 10725 assert!(error.contains("post conformance vector inventory drift")); 10726 } 10727 10728 #[test] 10729 fn comment_operation_authority_rejects_contract_drift() { 10730 let (manifest, vector) = current_comment_authority(); 10731 validate_comment_operation_inventory(&manifest, &vector) 10732 .expect("current Comment operation authority"); 10733 10734 let (mut manifest, vector) = current_comment_authority(); 10735 manifest 10736 .operations 10737 .remove("social_comment_build_authored_draft"); 10738 let error = validate_comment_operation_inventory(&manifest, &vector) 10739 .expect_err("missing Comment operation must fail"); 10740 assert!( 10741 error.contains("comment operation authority drift"), 10742 "{error}" 10743 ); 10744 10745 let (mut manifest, vector) = current_comment_authority(); 10746 let renamed = manifest 10747 .operations 10748 .remove("social_comment_project_verified_event") 10749 .expect("Comment projection operation"); 10750 manifest 10751 .operations 10752 .insert("social_comment_project_event".to_string(), renamed); 10753 let error = validate_comment_operation_inventory(&manifest, &vector) 10754 .expect_err("renamed Comment operation must fail"); 10755 assert!( 10756 error.contains("comment operation authority drift"), 10757 "{error}" 10758 ); 10759 assert!(error.contains("social_comment_project_event"), "{error}"); 10760 10761 let (mut manifest, vector) = current_comment_authority(); 10762 manifest 10763 .operations 10764 .get_mut("social_comment_verify_and_admit_event") 10765 .expect("Comment admission operation") 10766 .signing = "none".to_string(); 10767 let error = validate_comment_operation_inventory(&manifest, &vector) 10768 .expect_err("Comment operation metadata drift must fail"); 10769 assert!(error.contains("signing drift"), "{error}"); 10770 10771 let (mut manifest, vector) = current_comment_authority(); 10772 manifest 10773 .shared_types 10774 .public 10775 .retain(|value| value != "RadrootsInboundNip22TopLevelEventReference"); 10776 let error = validate_comment_operation_inventory(&manifest, &vector) 10777 .expect_err("required Comment public type removal must fail"); 10778 assert!( 10779 error.contains( 10780 "comment operation authority requires shared public type RadrootsInboundNip22TopLevelEventReference" 10781 ), 10782 "{error}" 10783 ); 10784 } 10785 10786 #[test] 10787 fn comment_operation_authority_rejects_vector_inventory_drift() { 10788 let (manifest, mut vector) = current_comment_authority(); 10789 vector 10790 .vectors 10791 .iter_mut() 10792 .find(|entry| entry.id == "authored_top_event_listing") 10793 .expect("authored top-level event case") 10794 .id = "authored_top_event_listing_replacement".to_string(); 10795 let error = validate_comment_operation_inventory(&manifest, &vector) 10796 .expect_err("same-count Comment vector ID replacement must fail"); 10797 assert!( 10798 error.contains("comment conformance vector inventory drift"), 10799 "{error}" 10800 ); 10801 10802 let (manifest, mut vector) = current_comment_authority(); 10803 vector.vectors.push(ConformanceVectorEntry { 10804 id: "unclaimed_comment_case".to_string(), 10805 kind: "social.comment.project_verified_event.shadow".to_string(), 10806 input: Value::Object(Default::default()), 10807 expected: Some(Value::Object(Default::default())), 10808 expected_error_contains: None, 10809 }); 10810 let error = validate_comment_operation_inventory(&manifest, &vector) 10811 .expect_err("unclaimed Comment vector kind must fail"); 10812 assert!( 10813 error.contains("is not claimed by exactly one operation"), 10814 "{error}" 10815 ); 10816 } 10817 10818 #[test] 10819 fn comment_vector_namespace_rejects_legacy_owners() { 10820 let canonical = PathBuf::from(COMMENT_CONFORMANCE_VECTOR_RELATIVE); 10821 let legacy = PathBuf::from("contracts/conformance/vectors/social/mvp.v1.json"); 10822 let vector = ConformanceVectorFile { 10823 suite: "legacy".to_string(), 10824 contract_version: "1.0.0".to_string(), 10825 vectors: vec![ConformanceVectorEntry { 10826 id: "legacy_comment".to_string(), 10827 kind: "social.comment.build_tags".to_string(), 10828 input: Value::Object(Default::default()), 10829 expected: Some(Value::Object(Default::default())), 10830 expected_error_contains: None, 10831 }], 10832 }; 10833 10834 let error = validate_comment_vector_namespace(&legacy, &canonical, &vector) 10835 .expect_err("legacy Comment vector namespace must fail"); 10836 assert!(error.contains("outside canonical vector"), "{error}"); 10837 validate_comment_vector_namespace(&canonical, &canonical, &vector) 10838 .expect("canonical Comment vector owns the namespace"); 10839 } 10840 10841 #[test] 10842 fn deletion_operation_authority_rejects_contract_drift() { 10843 let (manifest, request_vector, suppression_vector) = current_deletion_authority(); 10844 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 10845 .expect("current deletion operation authority"); 10846 10847 let (mut manifest, request_vector, suppression_vector) = current_deletion_authority(); 10848 manifest 10849 .operations 10850 .remove("social_deletion_request_project_verified_event"); 10851 let error = 10852 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 10853 .expect_err("missing deletion operation must fail"); 10854 assert!( 10855 error.contains("deletion operation authority drift"), 10856 "{error}" 10857 ); 10858 10859 let (mut manifest, request_vector, suppression_vector) = current_deletion_authority(); 10860 manifest 10861 .shared_types 10862 .public 10863 .push("RadrootsNip09DeletionUnauthorizedEffect".to_string()); 10864 let error = 10865 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 10866 .expect_err("unexpected deletion public type must fail"); 10867 assert!( 10868 error.contains("deletion operation public-type authority drift"), 10869 "{error}" 10870 ); 10871 10872 let (mut manifest, request_vector, suppression_vector) = current_deletion_authority(); 10873 manifest 10874 .operations 10875 .get_mut("social_deletion_request_verify_and_admit_event") 10876 .expect("admission operation") 10877 .conformance 10878 .case_kinds 10879 .pop(); 10880 let error = 10881 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 10882 .expect_err("missing deletion case kind must fail"); 10883 assert!(error.contains("conformance.case_kinds drift"), "{error}"); 10884 10885 let (mut manifest, request_vector, suppression_vector) = current_deletion_authority(); 10886 manifest 10887 .operations 10888 .get_mut("social_deletion_request_evaluate_suppression") 10889 .expect("suppression operation") 10890 .conformance 10891 .vector = DELETION_CONFORMANCE_VECTOR_RELATIVE.to_string(); 10892 let error = 10893 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 10894 .expect_err("suppression vector ownership drift must fail"); 10895 assert!(error.contains("conformance.vector drift"), "{error}"); 10896 } 10897 10898 #[test] 10899 fn deletion_operation_authority_rejects_vector_inventory_drift() { 10900 let (manifest, mut request_vector, suppression_vector) = current_deletion_authority(); 10901 request_vector 10902 .vectors 10903 .iter_mut() 10904 .find(|entry| entry.id == "nip09_authored_event_target_min_kind_empty_content") 10905 .expect("authored deletion vector") 10906 .id = "nip09_authored_event_target_min_kind_replacement".to_string(); 10907 let error = 10908 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 10909 .expect_err("same-count deletion vector ID replacement must fail"); 10910 assert!( 10911 error.contains("deletion conformance vector inventory drift"), 10912 "{error}" 10913 ); 10914 10915 let (manifest, mut request_vector, suppression_vector) = current_deletion_authority(); 10916 let source = request_vector 10917 .vectors 10918 .iter() 10919 .find(|entry| entry.id == "nip09_project_signed_event_target_without_k") 10920 .expect("valid signed deletion vector"); 10921 let input = source.input.clone(); 10922 let expected = source.expected.clone(); 10923 request_vector.vectors.push(ConformanceVectorEntry { 10924 id: "nip09_unclaimed_case".to_string(), 10925 kind: "social.deletion_request.unclaimed.valid".to_string(), 10926 input, 10927 expected, 10928 expected_error_contains: None, 10929 }); 10930 let error = 10931 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 10932 .expect_err("unclaimed deletion vector kind must fail"); 10933 assert!( 10934 error.contains("is not claimed by exactly one operation"), 10935 "{error}" 10936 ); 10937 10938 let (manifest, request_vector, mut suppression_vector) = current_deletion_authority(); 10939 suppression_vector 10940 .vectors 10941 .iter_mut() 10942 .find(|entry| entry.id == "nip09_suppress_no_requests_visible") 10943 .expect("visible suppression vector") 10944 .id = "nip09_suppress_replacement_visible".to_string(); 10945 let error = 10946 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 10947 .expect_err("same-count suppression vector ID replacement must fail"); 10948 assert!( 10949 error.contains("deletion suppression conformance vector inventory drift"), 10950 "{error}" 10951 ); 10952 } 10953 10954 #[test] 10955 fn deletion_operation_authority_rejects_generation_and_effect_metadata() { 10956 let (manifest, mut request_vector, suppression_vector) = current_deletion_authority(); 10957 request_vector 10958 .vectors 10959 .iter_mut() 10960 .find(|entry| entry.id == "nip09_authored_event_target_min_kind_empty_content") 10961 .expect("authored deletion vector") 10962 .input 10963 .as_object_mut() 10964 .expect("authored input") 10965 .insert("SeEd".to_string(), Value::from(7_u64)); 10966 let error = 10967 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 10968 .expect_err("generation seed metadata must fail"); 10969 assert!(error.contains("forbidden metadata key"), "{error}"); 10970 10971 let (manifest, mut request_vector, suppression_vector) = current_deletion_authority(); 10972 request_vector 10973 .vectors 10974 .iter_mut() 10975 .find(|entry| entry.id == "nip09_project_signed_event_target_without_k") 10976 .expect("signed deletion vector") 10977 .input 10978 .as_object_mut() 10979 .expect("signed input") 10980 .insert("trace".to_string(), Value::Bool(true)); 10981 let error = 10982 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 10983 .expect_err("extra signed input key must fail"); 10984 assert!(error.contains("input keys drift"), "{error}"); 10985 10986 let (manifest, mut request_vector, suppression_vector) = current_deletion_authority(); 10987 request_vector 10988 .vectors 10989 .iter_mut() 10990 .find(|entry| entry.id == "nip09_project_signed_event_target_without_k") 10991 .expect("signed deletion vector") 10992 .expected 10993 .as_mut() 10994 .expect("projection expected") 10995 .as_object_mut() 10996 .expect("projection expected object") 10997 .insert("AuThOrIzAtIoN".to_string(), Value::Bool(true)); 10998 let error = 10999 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 11000 .expect_err("effect-authority output must fail"); 11001 assert!(error.contains("forbidden metadata key"), "{error}"); 11002 11003 let (manifest, mut request_vector, suppression_vector) = current_deletion_authority(); 11004 request_vector 11005 .vectors 11006 .iter_mut() 11007 .find(|entry| entry.id == "nip09_admit_signed_event_target") 11008 .expect("admission deletion vector") 11009 .input 11010 .as_object_mut() 11011 .expect("signed input") 11012 .insert( 11013 "event_json".to_string(), 11014 Value::String(r#"{"content":"NSEC1FORBIDDEN"}"#.to_string()), 11015 ); 11016 let error = 11017 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 11018 .expect_err("nsec material must fail"); 11019 assert!(error.contains("forbidden nsec material"), "{error}"); 11020 11021 let (manifest, mut request_vector, suppression_vector) = current_deletion_authority(); 11022 request_vector 11023 .vectors 11024 .iter_mut() 11025 .find(|entry| entry.id == "nip09_admit_signed_event_target") 11026 .expect("admission deletion vector") 11027 .input 11028 .as_object_mut() 11029 .expect("signed input") 11030 .insert( 11031 "event_json".to_string(), 11032 Value::String( 11033 r#"{"content":"prefix10C5304D6C9AE3A1A16F7860F1CC8F5E3A76225A2663B3A989A0D775919B7DF5suffix"}"# 11034 .to_string(), 11035 ), 11036 ); 11037 let error = 11038 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 11039 .expect_err("approved fixture secret material must fail"); 11040 assert!( 11041 error.contains("forbidden approved fixture secret material"), 11042 "{error}" 11043 ); 11044 } 11045 11046 #[test] 11047 fn deletion_suppression_authority_rejects_shape_and_material_drift() { 11048 let (manifest, request_vector, mut suppression_vector) = current_deletion_authority(); 11049 suppression_vector.suite = "nip09_suppression_shadow".to_string(); 11050 let error = 11051 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 11052 .expect_err("suppression suite drift must fail"); 11053 assert!( 11054 error.contains("deletion suppression conformance suite drift"), 11055 "{error}" 11056 ); 11057 11058 let (manifest, request_vector, mut suppression_vector) = current_deletion_authority(); 11059 suppression_vector 11060 .vectors 11061 .iter_mut() 11062 .find(|entry| entry.id == "nip09_suppress_no_requests_visible") 11063 .expect("visible suppression vector") 11064 .input 11065 .as_object_mut() 11066 .expect("suppression input") 11067 .insert("seed".to_string(), Value::from(7_u64)); 11068 let error = 11069 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 11070 .expect_err("suppression generation metadata must fail"); 11071 assert!(error.contains("forbidden metadata key"), "{error}"); 11072 11073 let (manifest, request_vector, mut suppression_vector) = current_deletion_authority(); 11074 suppression_vector 11075 .vectors 11076 .iter_mut() 11077 .find(|entry| entry.id == "nip09_suppress_same_author_event_reference") 11078 .expect("event-reference suppression vector") 11079 .expected 11080 .as_mut() 11081 .expect("suppression expected") 11082 .as_object_mut() 11083 .expect("suppression expected object") 11084 .get_mut("event_reference") 11085 .expect("event-reference evidence") 11086 .as_object_mut() 11087 .expect("event-reference object") 11088 .insert("request_id".to_string(), Value::String("0".repeat(64))); 11089 let error = 11090 validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector) 11091 .expect_err("evidence not bound to an input request must fail"); 11092 assert!(error.contains("must identify an input request"), "{error}"); 11093 } 11094 11095 #[test] 11096 fn deletion_vector_namespace_rejects_alternate_owners() { 11097 let canonical_request = PathBuf::from(DELETION_CONFORMANCE_VECTOR_RELATIVE); 11098 let canonical_suppression = PathBuf::from(DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE); 11099 let alternate = PathBuf::from("contracts/conformance/vectors/social/mvp.v1.json"); 11100 let vector = ConformanceVectorFile { 11101 suite: "alternate".to_string(), 11102 contract_version: "1.0.0".to_string(), 11103 vectors: vec![ConformanceVectorEntry { 11104 id: "nip09_alternate".to_string(), 11105 kind: "social.deletion_request.build_authored_draft.valid".to_string(), 11106 input: Value::Object(Default::default()), 11107 expected: Some(Value::Object(Default::default())), 11108 expected_error_contains: None, 11109 }], 11110 }; 11111 11112 let error = validate_deletion_vector_namespace( 11113 &alternate, 11114 &canonical_request, 11115 &canonical_suppression, 11116 &vector, 11117 ) 11118 .expect_err("alternate deletion vector namespace must fail"); 11119 assert!(error.contains("outside canonical vectors"), "{error}"); 11120 validate_deletion_vector_namespace( 11121 &canonical_request, 11122 &canonical_request, 11123 &canonical_suppression, 11124 &vector, 11125 ) 11126 .expect("canonical deletion vector owns the namespace"); 11127 validate_deletion_vector_namespace( 11128 &canonical_suppression, 11129 &canonical_request, 11130 &canonical_suppression, 11131 &vector, 11132 ) 11133 .expect("canonical deletion suppression vector owns the namespace"); 11134 } 11135 11136 #[test] 11137 fn food_availability_operation_authority_rejects_contract_drift() { 11138 let (manifest, vector) = current_food_availability_authority(); 11139 validate_food_availability_operation_inventory(&manifest, &vector) 11140 .expect("current FoodAvailability operation authority"); 11141 11142 let (mut manifest, vector) = current_food_availability_authority(); 11143 manifest 11144 .operations 11145 .remove("food_availability_build_authored_draft"); 11146 let error = validate_food_availability_operation_inventory(&manifest, &vector) 11147 .expect_err("missing FoodAvailability operation must fail"); 11148 assert!( 11149 error.contains("food availability operation authority drift"), 11150 "{error}" 11151 ); 11152 11153 let (mut manifest, vector) = current_food_availability_authority(); 11154 manifest 11155 .shared_types 11156 .public 11157 .retain(|value| value != "RadrootsFoodAvailabilityRevisionError"); 11158 let error = validate_food_availability_operation_inventory(&manifest, &vector) 11159 .expect_err("missing FoodAvailability public type must fail"); 11160 assert!( 11161 error.contains( 11162 "food availability operation authority requires shared public type RadrootsFoodAvailabilityRevisionError" 11163 ), 11164 "{error}" 11165 ); 11166 11167 let (mut manifest, vector) = current_food_availability_authority(); 11168 manifest 11169 .operations 11170 .get_mut("food_availability_validate_revision") 11171 .expect("revision operation") 11172 .conformance 11173 .case_kinds 11174 .pop(); 11175 let error = validate_food_availability_operation_inventory(&manifest, &vector) 11176 .expect_err("missing FoodAvailability case kind must fail"); 11177 assert!(error.contains("conformance.case_kinds drift"), "{error}"); 11178 } 11179 11180 #[test] 11181 fn food_availability_operation_authority_rejects_vector_inventory_drift() { 11182 let (manifest, mut vector) = current_food_availability_authority(); 11183 vector 11184 .vectors 11185 .iter_mut() 11186 .find(|entry| entry.id == "food_admission_normalizes_decimal_currency_014") 11187 .expect("normalization vector") 11188 .id = "food_admission_normalizes_decimal_currency_replacement".to_string(); 11189 let error = validate_food_availability_operation_inventory(&manifest, &vector) 11190 .expect_err("same-count FoodAvailability vector replacement must fail"); 11191 assert!( 11192 error.contains("food availability conformance vector inventory drift"), 11193 "{error}" 11194 ); 11195 11196 let (manifest, mut vector) = current_food_availability_authority(); 11197 vector.vectors.push(ConformanceVectorEntry { 11198 id: "food_unclaimed_case".to_string(), 11199 kind: "food_availability.unclaimed.valid".to_string(), 11200 input: Value::Object(Default::default()), 11201 expected: Some(Value::Object(Default::default())), 11202 expected_error_contains: None, 11203 }); 11204 let error = validate_food_availability_operation_inventory(&manifest, &vector) 11205 .expect_err("unclaimed FoodAvailability vector kind must fail"); 11206 assert!( 11207 error.contains("is not claimed by exactly one operation"), 11208 "{error}" 11209 ); 11210 } 11211 11212 #[test] 11213 fn version_governance_rejects_contract_workspace_and_lock_drift() { 11214 let root = create_synthetic_workspace("version_governance_drift"); 11215 let mut bundle = load_contract_bundle(&root).expect("load contract"); 11216 bundle.version.contract.version = "1.0.1".to_string(); 11217 let contract_error = validate_contract_version_lockstep(&bundle) 11218 .expect_err("contract header drift must fail"); 11219 assert!(contract_error.contains("must match manifest contract version")); 11220 11221 let member_path = root.join("crates/a/Cargo.toml"); 11222 let member = fs::read_to_string(&member_path).expect("read member manifest"); 11223 write_file( 11224 &member_path, 11225 &member.replace("version = \"1.0.0\"", "version.workspace = true"), 11226 ); 11227 let member_error = validate_workspace_version_lockstep(&root, "1.0.0") 11228 .expect_err("inherited member version must fail"); 11229 assert!(member_error.contains("must set an explicit package version")); 11230 write_file(&member_path, &member); 11231 11232 let workspace_path = root.join("Cargo.toml"); 11233 let workspace = fs::read_to_string(&workspace_path).expect("read workspace manifest"); 11234 write_file( 11235 &workspace_path, 11236 &workspace.replacen( 11237 "radroots_a = { path = \"crates/a\", version = \"=1.0.0\" }", 11238 "radroots_a = { path = \"crates/a\", version = \"1.0.0\" }", 11239 1, 11240 ), 11241 ); 11242 let requirement_error = validate_workspace_version_lockstep(&root, "1.0.0") 11243 .expect_err("non-exact internal dependency must fail"); 11244 assert!(requirement_error.contains("exact requirement =1.0.0")); 11245 write_file(&workspace_path, &workspace); 11246 11247 let lock_path = root.join("Cargo.lock"); 11248 let lock = fs::read_to_string(&lock_path).expect("read Cargo.lock"); 11249 write_file(&lock_path, &lock.replacen("1.0.0", "1.0.1", 1)); 11250 let lock_error = validate_workspace_version_lockstep(&root, "1.0.0") 11251 .expect_err("lockfile version drift must fail"); 11252 assert!(lock_error.contains("Cargo.lock package radroots_a version")); 11253 11254 write_file( 11255 &root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"), 11256 r#"spec_id = "radroots.crates.release.v1" 11257 package_count = 0 11258 package = [] 11259 11260 [repositories.lib] 11261 version = "0.1.0-alpha" 11262 packages = [] 11263 11264 [repositories.sdk] 11265 version = "0.1.0" 11266 packages = [] 11267 "#, 11268 ); 11269 let architecture_error = validate_workspace_version_lockstep(&root, "1.0.0") 11270 .expect_err("repository version authority must override protocol version"); 11271 assert!(architecture_error.contains("must match library repository version 0.1.0-alpha")); 11272 11273 assert!(parse_semver_version("01.0.0").is_err()); 11274 assert!(parse_semver_version("1.0").is_err()); 11275 assert!(parse_semver_version("1.0.0-alpha_1").is_err()); 11276 let _ = fs::remove_dir_all(root); 11277 } 11278 11279 #[test] 11280 fn replica_contract_governance_rejects_metadata_policy_and_transfer_drift() { 11281 let root = create_synthetic_workspace("replica_contract_drift"); 11282 let bundle = load_contract_bundle(&root).expect("load synthetic contract"); 11283 validate_replica_contract(&bundle, &root).expect("validate replica contract"); 11284 11285 let assert_replica_error = |expected: &str, mutator: fn(&mut ContractBundle)| { 11286 let mut bundle = load_contract_bundle(&root).expect("load synthetic contract"); 11287 mutator(&mut bundle); 11288 let error = validate_replica_contract(&bundle, &root) 11289 .expect_err("replica contract drift must fail"); 11290 assert!( 11291 error.contains(expected), 11292 "expected `{expected}` in `{error}`" 11293 ); 11294 }; 11295 11296 assert_replica_error("schema_version must be 1", |bundle| { 11297 bundle.replica.schema_version = 2; 11298 }); 11299 assert_replica_error("name must be radroots_replica_contract", |bundle| { 11300 bundle.replica.contract.name = "replica".to_string(); 11301 }); 11302 assert_replica_error("must match manifest contract version", |bundle| { 11303 bundle.replica.contract.version = "1.0.1".to_string(); 11304 }); 11305 assert_replica_error("purpose is required", |bundle| { 11306 bundle.replica.contract.purpose.clear(); 11307 }); 11308 assert_replica_error("crate_family.schema", |bundle| { 11309 bundle.replica.crate_family.schema = "radroots_b".to_string(); 11310 }); 11311 assert_replica_error("must name a workspace package", |bundle| { 11312 bundle.replica.crate_family.schema = "radroots_missing".to_string(); 11313 bundle 11314 .manifest 11315 .surface 11316 .internal_replica_crates 11317 .as_mut() 11318 .expect("replica family") 11319 .schema = "radroots_missing".to_string(); 11320 }); 11321 assert_replica_error("policy.replica is required", |bundle| { 11322 bundle.manifest.policy.replica = None; 11323 }); 11324 assert_replica_error("transport_agnostic_sync_core", |bundle| { 11325 bundle.replica.policy.transport_agnostic_sync_core = false; 11326 }); 11327 assert_replica_error("profile_event_emission must be excluded", |bundle| { 11328 bundle.replica.policy.profile_event_emission = "included".to_string(); 11329 }); 11330 assert_replica_error("unknown_sync_request_fields must be reject", |bundle| { 11331 bundle.replica.policy.unknown_sync_request_fields = "ignore".to_string(); 11332 }); 11333 assert_replica_error( 11334 "classified_listing_signature_verification must be required_before_state", 11335 |bundle| { 11336 bundle 11337 .replica 11338 .policy 11339 .classified_listing_signature_verification = "unchecked".to_string(); 11340 }, 11341 ); 11342 assert_replica_error( 11343 "classified_listing_head_selection must be raw_before_profile", 11344 |bundle| { 11345 bundle.replica.policy.classified_listing_head_selection = 11346 "profile_before_raw".to_string(); 11347 }, 11348 ); 11349 assert_replica_error( 11350 "classified_listing_operational_projection must be operational_partition_only", 11351 |bundle| { 11352 bundle 11353 .replica 11354 .policy 11355 .classified_listing_operational_projection = "all_partitions".to_string(); 11356 }, 11357 ); 11358 assert_replica_error( 11359 "classified_listing_excluded_or_rejected_head must be remove_projection_and_advance", 11360 |bundle| { 11361 bundle 11362 .replica 11363 .policy 11364 .classified_listing_excluded_or_rejected_head = "retain_projection".to_string(); 11365 }, 11366 ); 11367 assert_replica_error( 11368 "classified_listing_head_only_ingest must be reject_require_profile_aware", 11369 |bundle| { 11370 bundle.replica.policy.classified_listing_head_only_ingest = 11371 "allow_head_only".to_string(); 11372 }, 11373 ); 11374 assert_replica_error( 11375 "legacy_bare_envelope_ingest must be explicit_non_default_feature_only", 11376 |bundle| { 11377 bundle.replica.policy.legacy_bare_envelope_ingest = "default".to_string(); 11378 }, 11379 ); 11380 assert_replica_error("legacy_ingest_feature must be legacy-ingest", |bundle| { 11381 bundle.replica.policy.legacy_ingest_feature = "std".to_string(); 11382 }); 11383 assert_replica_error("phase_1_ingest_replacement must be none", |bundle| { 11384 bundle.replica.policy.phase_1_ingest_replacement = "legacy".to_string(); 11385 }); 11386 assert_replica_error( 11387 "future_product_ingest_input must be store_produced_verified_valid_visible_admission", 11388 |bundle| { 11389 bundle.replica.policy.future_product_ingest_input = "bare_envelope".to_string(); 11390 }, 11391 ); 11392 assert_replica_error("transfer.version must be 2", |bundle| { 11393 bundle.replica.transfer.version = 1; 11394 }); 11395 assert_replica_error("transfer.constant", |bundle| { 11396 bundle.replica.transfer.constant = "REPLICA_VERSION".to_string(); 11397 }); 11398 assert_replica_error("transfer.source", |bundle| { 11399 bundle.replica.transfer.source = "crates/a/src/types.rs".to_string(); 11400 }); 11401 11402 let source_path = root.join("crates/b/src/types.rs"); 11403 let source = fs::read_to_string(&source_path).expect("read replica types source"); 11404 write_file( 11405 &source_path, 11406 &source.replace( 11407 "pub const RADROOTS_REPLICA_TRANSFER_VERSION: u32 = 2;", 11408 "pub const RADROOTS_REPLICA_TRANSFER_VERSION: u32 = 1;", 11409 ), 11410 ); 11411 let bundle = load_contract_bundle(&root).expect("load source-drift contract"); 11412 let source_error = validate_replica_contract(&bundle, &root) 11413 .expect_err("source constant version drift must fail"); 11414 assert!(source_error.contains("source constant")); 11415 assert!(source_error.contains("must match contract version 2")); 11416 11417 let _ = fs::remove_dir_all(root); 11418 } 11419 11420 #[test] 11421 fn replica_policy_source_witnesses_reject_runtime_drift() { 11422 let root = create_synthetic_workspace("replica_policy_source_drift"); 11423 let cargo_path = root.join("crates/b/Cargo.toml"); 11424 let lib_path = root.join("crates/b/src/lib.rs"); 11425 let types_path = root.join("crates/b/src/types.rs"); 11426 let emit_path = root.join("crates/b/src/emit.rs"); 11427 let cargo = fs::read_to_string(&cargo_path).expect("read replica cargo manifest"); 11428 let lib = fs::read_to_string(&lib_path).expect("read replica lib source"); 11429 let types = fs::read_to_string(&types_path).expect("read replica types source"); 11430 let emit = fs::read_to_string(&emit_path).expect("read replica emit source"); 11431 11432 write_file( 11433 &cargo_path, 11434 &cargo.replace("std = []", "std = [\"legacy-ingest\"]"), 11435 ); 11436 let bundle = 11437 load_contract_bundle(&root).expect("load transitive default-feature drift contract"); 11438 let default_feature_error = validate_replica_contract(&bundle, &root) 11439 .expect_err("transitively default legacy ingest feature must fail"); 11440 assert!(default_feature_error.contains("must not be enabled by default features")); 11441 11442 write_file(&cargo_path, &cargo); 11443 write_file( 11444 &lib_path, 11445 &format!( 11446 "{}\n/*\n#[cfg(feature = \"legacy-ingest\")]\npub mod ingest;\n*/\n", 11447 lib.replace( 11448 "#[cfg(feature = \"legacy-ingest\")]\npub mod ingest;", 11449 "#[cfg(feature = \"std\")]\npub mod ingest;", 11450 ) 11451 ), 11452 ); 11453 let bundle = load_contract_bundle(&root).expect("load ingest-module drift contract"); 11454 let module_error = validate_replica_contract(&bundle, &root) 11455 .expect_err("comment-only legacy guard witness must fail"); 11456 assert!(module_error.contains("must be guarded by exact")); 11457 11458 write_file( 11459 &lib_path, 11460 &format!("{lib}\npub use ingest::RadrootsReplicaIngestOutcome;\n"), 11461 ); 11462 let bundle = load_contract_bundle(&root).expect("load second-reexport drift contract"); 11463 let reexport_error = validate_replica_contract(&bundle, &root) 11464 .expect_err("ungated second ingest re-export must fail"); 11465 assert!(reexport_error.contains("every public replica ingest re-export")); 11466 11467 write_file( 11468 &lib_path, 11469 &lib.replacen( 11470 "#[cfg(feature = \"legacy-ingest\")]\npub use ingest::{", 11471 "#[cfg(any(feature = \"legacy-ingest\", feature = \"std\"))]\npub use ingest::{", 11472 1, 11473 ), 11474 ); 11475 let bundle = load_contract_bundle(&root).expect("load broadened-reexport contract"); 11476 let broadened_error = validate_replica_contract(&bundle, &root) 11477 .expect_err("broadened ingest re-export guard must fail"); 11478 assert!(broadened_error.contains("every public replica ingest re-export")); 11479 11480 write_file( 11481 &lib_path, 11482 &format!( 11483 "{lib}\npub mod nested {{\n pub use super::ingest::RadrootsReplicaIngestOutcome;\n}}\n" 11484 ), 11485 ); 11486 let bundle = load_contract_bundle(&root).expect("load nested-reexport drift contract"); 11487 let nested_error = validate_replica_contract(&bundle, &root) 11488 .expect_err("ungated nested public ingest re-export must fail"); 11489 assert!(nested_error.contains("every public replica ingest re-export")); 11490 11491 write_file(&lib_path, &lib); 11492 11493 write_file( 11494 &types_path, 11495 &types.replace( 11496 "#[serde(deny_unknown_fields)]\npub struct RadrootsReplicaSyncOptions", 11497 "pub struct RadrootsReplicaSyncOptions", 11498 ), 11499 ); 11500 let bundle = load_contract_bundle(&root).expect("load attribute-drift contract"); 11501 let attribute_error = validate_replica_contract(&bundle, &root) 11502 .expect_err("missing fail-closed request attribute must fail"); 11503 assert!(attribute_error.contains("RadrootsReplicaSyncOptions")); 11504 assert!(attribute_error.contains("immediately before")); 11505 11506 write_file( 11507 &types_path, 11508 &format!("{types}\npub struct include_profiles;\n"), 11509 ); 11510 let bundle = load_contract_bundle(&root).expect("load retired-option contract"); 11511 let retired_error = validate_replica_contract(&bundle, &root) 11512 .expect_err("retired include_profiles identifier must fail"); 11513 assert!(retired_error.contains("include_profiles is forbidden")); 11514 11515 write_file(&types_path, &types); 11516 write_file( 11517 &emit_path, 11518 &emit.replace( 11519 "#[cfg(test)]\nmod tests {}", 11520 "fn emit_profile_event() {}\n\n#[cfg(test)]\nmod tests {}", 11521 ), 11522 ); 11523 let bundle = load_contract_bundle(&root).expect("load profile-emitter contract"); 11524 let profile_error = validate_replica_contract(&bundle, &root) 11525 .expect_err("Profile-related production emitter must fail"); 11526 assert!(profile_error.contains("Profile-related identifier emit_profile_event")); 11527 11528 write_file( 11529 &emit_path, 11530 &emit.replace( 11531 "#[cfg(test)]\nmod tests {}", 11532 "fn emit_kind_zero() { let _event = Event { kind: 0 }; }\n\n#[cfg(test)]\nmod tests {}", 11533 ), 11534 ); 11535 let bundle = load_contract_bundle(&root).expect("load literal-kind-zero contract"); 11536 let kind_error = validate_replica_contract(&bundle, &root) 11537 .expect_err("literal kind-0 production emitter must fail"); 11538 assert!(kind_error.contains("must not construct a literal kind-0 event")); 11539 11540 let _ = fs::remove_dir_all(root); 11541 } 11542 11543 #[test] 11544 fn replica_contract_is_required_and_rejects_unknown_fields() { 11545 let missing_root = create_synthetic_workspace("replica_contract_missing"); 11546 fs::remove_file(missing_root.join(REPLICA_CONTRACT_RELATIVE)) 11547 .expect("remove replica contract"); 11548 let missing_error = load_contract_bundle(&missing_root) 11549 .expect_err("missing replica contract must fail bundle loading"); 11550 assert!(missing_error.contains(REPLICA_CONTRACT_RELATIVE)); 11551 let _ = fs::remove_dir_all(missing_root); 11552 11553 let unknown_root = create_synthetic_workspace("replica_contract_unknown_field"); 11554 let replica_path = unknown_root.join(REPLICA_CONTRACT_RELATIVE); 11555 let replica = fs::read_to_string(&replica_path).expect("read replica contract"); 11556 write_file(&replica_path, &format!("{replica}unexpected = true\n")); 11557 let unknown_error = load_contract_bundle(&unknown_root) 11558 .expect_err("unknown replica contract field must fail bundle loading"); 11559 assert!(unknown_error.contains(REPLICA_CONTRACT_RELATIVE)); 11560 assert!(unknown_error.contains("unexpected")); 11561 let _ = fs::remove_dir_all(unknown_root); 11562 } 11563 11564 #[test] 11565 fn conformance_vector_mirrors_are_required_even_when_parent_is_deleted() { 11566 let root = create_synthetic_workspace("required_conformance_mirrors"); 11567 validate_conformance_vector_mirrors(&root).expect("validate synthetic mirrors"); 11568 11569 let (_, mirror_relative) = CONFORMANCE_VECTOR_MIRRORS[0]; 11570 let mirror_path = root.join(mirror_relative); 11571 fs::remove_file(&mirror_path).expect("remove required mirror"); 11572 let missing_file_error = validate_conformance_vector_mirrors(&root) 11573 .expect_err("missing required mirror file must fail"); 11574 assert!(missing_file_error.contains(&format!("read {mirror_relative}"))); 11575 11576 write_file(&mirror_path, SYNTHETIC_CONFORMANCE_VECTOR); 11577 fs::remove_dir_all(mirror_path.parent().expect("mirror parent")) 11578 .expect("remove required mirror parent"); 11579 let missing_parent_error = validate_conformance_vector_mirrors(&root) 11580 .expect_err("missing required mirror parent must fail"); 11581 assert!(missing_parent_error.contains(&format!("read {mirror_relative}"))); 11582 11583 let _ = fs::remove_dir_all(root); 11584 } 11585 11586 #[test] 11587 fn release_record_and_conformance_mirror_validation_reject_drift() { 11588 let root = create_synthetic_workspace("release_record_drift"); 11589 let bundle = load_contract_bundle(&root).expect("load synthetic contract"); 11590 validate_release_record(&root, "1.0.0", &bundle.version.semver) 11591 .expect("validate release record"); 11592 11593 let record_path = root.join("contracts/releases/1.0.0.toml"); 11594 let record = fs::read_to_string(&record_path).expect("read release record"); 11595 write_file( 11596 &record_path, 11597 &record.replace("status = \"unreleased\"", "status = \"pending\""), 11598 ); 11599 let status_error = validate_release_record(&root, "1.0.0", &bundle.version.semver) 11600 .expect_err("unsupported release status must fail"); 11601 assert!(status_error.contains("must be unreleased, released, or yanked")); 11602 11603 write_file( 11604 &record_path, 11605 &record.replace( 11606 "replica = \"contracts/replica.toml\"", 11607 "replica = \"contracts/replica-v1.toml\"", 11608 ), 11609 ); 11610 let replica_artifact_error = 11611 validate_release_record(&root, "1.0.0", &bundle.version.semver) 11612 .expect_err("noncanonical replica artifact must fail"); 11613 assert!(replica_artifact_error.contains( 11614 "release artifact path contracts/replica-v1.toml must use canonical path contracts/replica.toml" 11615 )); 11616 11617 write_file( 11618 &record_path, 11619 &record.replace( 11620 "operations = \"contracts/operations.toml\"", 11621 "operations = \"contracts/operations-v1.toml\"", 11622 ), 11623 ); 11624 let operations_artifact_error = 11625 validate_release_record(&root, "1.0.0", &bundle.version.semver) 11626 .expect_err("noncanonical operations artifact must fail"); 11627 assert!(operations_artifact_error.contains( 11628 "release artifact path contracts/operations-v1.toml must use canonical path contracts/operations.toml" 11629 )); 11630 11631 write_file(&record_path, &record); 11632 let operations_path = root.join("contracts").join("operations.toml"); 11633 let operations = fs::read_to_string(&operations_path).expect("read operations manifest"); 11634 fs::remove_file(&operations_path).expect("remove operations manifest"); 11635 let missing_operations_error = 11636 validate_release_record(&root, "1.0.0", &bundle.version.semver) 11637 .expect_err("missing operations artifact must fail"); 11638 assert!( 11639 missing_operations_error 11640 .contains("release artifact contracts/operations.toml does not exist") 11641 ); 11642 write_file(&operations_path, &operations); 11643 11644 write_file( 11645 &record_path, 11646 &record.replace("id = \"synthetic-major-release\"", "id = \"Bad_Id\""), 11647 ); 11648 let id_error = validate_release_record(&root, "1.0.0", &bundle.version.semver) 11649 .expect_err("invalid release change id must fail"); 11650 assert!(id_error.contains("lowercase kebab-case")); 11651 write_file(&record_path, &record); 11652 11653 write_file( 11654 &record_path, 11655 &record.replace( 11656 "semver_impacts = [\"breaking\"]", 11657 "semver_impacts = [\"fix\"]", 11658 ), 11659 ); 11660 let classification_error = validate_release_record(&root, "1.0.0", &bundle.version.semver) 11661 .expect_err("classification and semver impact mismatch must fail"); 11662 assert!(classification_error.contains("does not match its governed semver impacts")); 11663 11664 write_file( 11665 &record_path, 11666 &record.replace( 11667 "semver_impacts = [\"breaking\"]", 11668 "semver_impacts = [\"unknown-impact\"]", 11669 ), 11670 ); 11671 let impact_error = validate_release_record(&root, "1.0.0", &bundle.version.semver) 11672 .expect_err("unknown semver impact must fail"); 11673 assert!(impact_error.contains("is not governed by contracts/version.toml")); 11674 write_file(&record_path, &record); 11675 11676 write_file(&root.join(CHANGELOG_RELATIVE), "# Changelog\n"); 11677 let notes_error = validate_release_record(&root, "1.0.0", &bundle.version.semver) 11678 .expect_err("missing release notes must fail"); 11679 assert!(notes_error.contains("missing heading")); 11680 11681 let (canonical_relative, mirror_relative) = CONFORMANCE_VECTOR_MIRRORS[0]; 11682 write_file(&root.join(canonical_relative), "canonical\n"); 11683 write_file(&root.join(mirror_relative), "drifted\n"); 11684 let mirror_error = validate_conformance_vector_mirrors(&root) 11685 .expect_err("packaged conformance drift must fail"); 11686 assert!(mirror_error.contains("must exactly match")); 11687 11688 let _ = fs::remove_dir_all(root); 11689 } 11690 11691 #[test] 11692 fn calendar_operation_authority_reports_exact_signature_drift() { 11693 let root = workspace_root(); 11694 let mut bundle = load_contract_bundle(&root).expect("load contract"); 11695 let manifest = &mut bundle.operations_manifest; 11696 let shared_types = 11697 collect_non_empty_set(&manifest.shared_types.public, "shared_types.public") 11698 .expect("shared public types"); 11699 manifest 11700 .operations 11701 .get_mut("social_calendar_date_event_build_authored_draft") 11702 .expect("calendar authored operation") 11703 .outputs = vec!["NostrTags".to_string()]; 11704 11705 let error = validate_calendar_operation_authority(manifest, &shared_types) 11706 .expect_err("calendar operation signature drift"); 11707 assert!(error.contains("outputs drift")); 11708 } 11709 11710 #[test] 11711 fn calendar_operation_authority_reports_obsolete_operation_drift() { 11712 let root = workspace_root(); 11713 let mut bundle = load_contract_bundle(&root).expect("load contract"); 11714 let manifest = &mut bundle.operations_manifest; 11715 let shared_types = 11716 collect_non_empty_set(&manifest.shared_types.public, "shared_types.public") 11717 .expect("shared public types"); 11718 let obsolete = manifest 11719 .operations 11720 .remove("social_calendar_date_event_build_authored_draft") 11721 .expect("calendar authored operation"); 11722 manifest.operations.insert( 11723 "social_calendar_date_event_build_tags".to_string(), 11724 obsolete, 11725 ); 11726 11727 let error = validate_calendar_operation_authority(manifest, &shared_types) 11728 .expect_err("obsolete calendar operation drift"); 11729 assert!(error.contains("calendar operation authority drift")); 11730 assert!(error.contains("social_calendar_date_event_build_tags")); 11731 } 11732 11733 #[test] 11734 fn calendar_operation_authority_reports_obsolete_rsvp_operation_drift() { 11735 let root = workspace_root(); 11736 let mut bundle = load_contract_bundle(&root).expect("load contract"); 11737 let manifest = &mut bundle.operations_manifest; 11738 let shared_types = 11739 collect_non_empty_set(&manifest.shared_types.public, "shared_types.public") 11740 .expect("shared public types"); 11741 let obsolete = manifest 11742 .operations 11743 .remove("social_calendar_rsvp_build_authored_draft") 11744 .expect("calendar RSVP authored operation"); 11745 manifest 11746 .operations 11747 .insert("social_calendar_rsvp_build_tags".to_string(), obsolete); 11748 11749 let error = validate_calendar_operation_authority(manifest, &shared_types) 11750 .expect_err("obsolete calendar RSVP operation drift"); 11751 assert!(error.contains("calendar operation authority drift")); 11752 assert!(error.contains("social_calendar_rsvp_build_tags")); 11753 } 11754 11755 #[test] 11756 fn validate_current_canonical_event_boundary() { 11757 let root = workspace_root(); 11758 validate_canonical_event_boundary(&root).expect("validate canonical event boundary"); 11759 } 11760 11761 #[test] 11762 fn canonical_event_boundary_reports_row_drift() { 11763 let root = workspace_root(); 11764 let matrix_path = resolve_event_boundary_matrix_path(&root).expect("matrix path"); 11765 let raw = fs::read_to_string(&matrix_path).expect("read matrix"); 11766 let drifted = raw.replacen( 11767 "| message | 14 | Message |", 11768 "| message | 999 | Message |", 11769 1, 11770 ); 11771 let temp = temp_root("event_boundary_drift"); 11772 let override_path = temp.join("spec-coverage.md"); 11773 write_file(&override_path, &drifted); 11774 11775 let err = validate_canonical_event_boundary_at_path(&root, &override_path) 11776 .expect_err("message kind drift should fail"); 11777 assert!(err.contains("message kind drift")); 11778 11779 let _ = fs::remove_dir_all(temp); 11780 } 11781 11782 #[test] 11783 fn canonical_event_boundary_rejects_deletion_operation_drift() { 11784 let root = workspace_root(); 11785 let matrix_path = resolve_event_boundary_matrix_path(&root).expect("matrix path"); 11786 let raw = fs::read_to_string(&matrix_path).expect("read matrix"); 11787 let (preamble, table) = raw 11788 .split_once("## Coverage matrix") 11789 .expect("coverage matrix marker"); 11790 let drifted = format!( 11791 "{preamble}## Coverage matrix{}", 11792 table.replacen( 11793 "social.deletion_request.project_verified_event", 11794 "social.deletion_request.project_unverified_event", 11795 1, 11796 ) 11797 ); 11798 let temp = temp_root("deletion_event_boundary_drift"); 11799 let override_path = temp.join("spec-coverage.md"); 11800 write_file(&override_path, &drifted); 11801 11802 let error = validate_canonical_event_boundary_at_path(&root, &override_path) 11803 .expect_err("deletion operation drift must fail"); 11804 assert!(error.contains("deletion_request rpc drift"), "{error}"); 11805 11806 let _ = fs::remove_dir_all(temp); 11807 } 11808 11809 #[test] 11810 fn event_boundary_never_falls_back_to_parent_human_documentation() { 11811 let parent = temp_root("event_boundary_parent_docs"); 11812 let capsule = parent.join("oss/lib"); 11813 fs::create_dir_all(&capsule).expect("capsule root"); 11814 let parent_doc = parent.join( 11815 "docs/platform/canonical/open_source/radroots_v1_spec/02_public_contract_and_runtime/08_event_boundary_matrix.md", 11816 ); 11817 write_file(&parent_doc, &synthetic_event_boundary_matrix()); 11818 11819 let error = resolve_event_boundary_matrix_path(&capsule) 11820 .expect_err("parent human documentation must not become contract input"); 11821 assert!(error.contains(EVENT_BOUNDARY_MATRIX_RELATIVE)); 11822 let _ = fs::remove_dir_all(parent); 11823 } 11824 11825 #[test] 11826 fn validate_synthetic_operation_contract_bundle() { 11827 let root = create_synthetic_workspace("operation_contract_bundle"); 11828 add_operation_contract_files(&root); 11829 let bundle = load_contract_bundle(&root).expect("load contract"); 11830 validate_generic_contract_bundle(&bundle).expect("validate contract"); 11831 let _ = fs::remove_dir_all(root); 11832 } 11833 11834 #[test] 11835 fn parses_enum_variants_in_declared_order() { 11836 let source = r#" 11837 pub enum UnitDimension { 11838 Count, 11839 Mass, 11840 Volume, 11841 } 11842 "#; 11843 let enum_body = extract_enum_body(source, "UnitDimension").expect("enum body"); 11844 let variants = parse_enum_variants(enum_body); 11845 assert_eq!(variants, vec!["Count", "Mass", "Volume"]); 11846 } 11847 11848 #[test] 11849 fn fails_when_enum_order_does_not_match_contract() { 11850 let source = r#" 11851 pub enum UnitDimension { 11852 Mass, 11853 Count, 11854 Volume, 11855 } 11856 "#; 11857 let enum_body = extract_enum_body(source, "UnitDimension").expect("enum body"); 11858 let variants = parse_enum_variants(enum_body); 11859 let expected = CORE_UNIT_DIMENSION_ORDER 11860 .iter() 11861 .map(|item| (*item).to_string()) 11862 .collect::<Vec<_>>(); 11863 assert_ne!(variants, expected); 11864 } 11865 11866 #[test] 11867 fn coverage_policy_matches_non_simplex_workspace_crates() { 11868 let root = workspace_root(); 11869 let expected_names = 11870 coverage_required_workspace_crates(&root).expect("workspace coverage crates"); 11871 let policy = load_coverage_policy(&root.join("contracts")).expect("coverage policy"); 11872 let required_names = policy 11873 .required_crates() 11874 .expect("required crates") 11875 .into_iter() 11876 .collect::<BTreeSet<_>>(); 11877 assert_eq!(expected_names, required_names); 11878 assert!( 11879 required_names 11880 .iter() 11881 .all(|crate_name| !coverage_policy_excludes_workspace_crate(crate_name)) 11882 ); 11883 } 11884 11885 #[test] 11886 fn coverage_required_workspace_crates_excludes_non_policy_packages() { 11887 let root = temp_root("coverage_required_workspace_simplex"); 11888 write_file( 11889 &root.join("Cargo.toml"), 11890 r#"[workspace] 11891 members = ["crates/a", "crates/radroots_simplex_probe", "crates/simplex_probe"] 11892 resolver = "2" 11893 "#, 11894 ); 11895 write_file( 11896 &root.join("crates").join("a").join("Cargo.toml"), 11897 r#"[package] 11898 name = "radroots_a" 11899 version = "1.0.0" 11900 edition = "2024" 11901 "#, 11902 ); 11903 write_file( 11904 &root 11905 .join("crates") 11906 .join("radroots_simplex_probe") 11907 .join("Cargo.toml"), 11908 r#"[package] 11909 name = "radroots_simplex_probe" 11910 version = "1.0.0" 11911 edition = "2024" 11912 "#, 11913 ); 11914 write_file( 11915 &root.join("crates").join("simplex_probe").join("Cargo.toml"), 11916 r#"[package] 11917 name = "simplex_probe" 11918 version = "1.0.0" 11919 edition = "2024" 11920 "#, 11921 ); 11922 11923 let required = 11924 coverage_required_workspace_crates(&root).expect("workspace coverage crates"); 11925 assert_eq!( 11926 required, 11927 ["radroots_a".to_string()] 11928 .into_iter() 11929 .collect::<BTreeSet<_>>() 11930 ); 11931 assert!(coverage_policy_excludes_workspace_crate( 11932 "radroots_simplex_probe" 11933 )); 11934 assert!(coverage_policy_excludes_workspace_crate("simplex_probe")); 11935 assert!(!coverage_policy_excludes_workspace_crate("radroots_a")); 11936 11937 let _ = fs::remove_dir_all(root); 11938 } 11939 11940 #[test] 11941 fn coverage_required_crates_match_policy_required_status() { 11942 let root = workspace_root(); 11943 let contract_root = root.join("contracts"); 11944 let policy = load_coverage_policy(&contract_root).expect("coverage policy"); 11945 let required = CoverageRequiredFile { 11946 required: CoverageRequiredSection { 11947 crates: policy.required_crates().expect("coverage required"), 11948 }, 11949 }; 11950 let required_names = required 11951 .required 11952 .crates 11953 .into_iter() 11954 .collect::<BTreeSet<_>>(); 11955 let policy_required = policy 11956 .required_crates() 11957 .expect("policy required crates") 11958 .into_iter() 11959 .collect::<BTreeSet<_>>(); 11960 assert_eq!(required_names, policy_required); 11961 } 11962 11963 #[test] 11964 fn coverage_policy_required_crates_report_policy_errors() { 11965 let missing_root = temp_root("load_coverage_required_missing_policy"); 11966 let missing_err = 11967 load_coverage_policy(&missing_root).expect_err("missing policy should fail"); 11968 assert!(missing_err.contains("coverage.toml")); 11969 let _ = fs::remove_dir_all(&missing_root); 11970 11971 let duplicate_root = 11972 create_synthetic_workspace("load_coverage_required_duplicate_required"); 11973 let contract_root = duplicate_root.join("contracts"); 11974 let coverage_root = coverage_root(&contract_root); 11975 write_file( 11976 &coverage_root.join("coverage.toml"), 11977 "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\", \"radroots_a\"]\n", 11978 ); 11979 let duplicate_err = 11980 load_coverage_policy(&contract_root).expect_err("duplicate required crates"); 11981 assert!(duplicate_err.contains("duplicate crate")); 11982 let _ = fs::remove_dir_all(&duplicate_root); 11983 } 11984 11985 #[test] 11986 fn package_field_configured_accepts_workspace_table() { 11987 let mut package = toml::value::Table::new(); 11988 let mut repository = toml::value::Table::new(); 11989 repository.insert("workspace".to_string(), toml::Value::Boolean(true)); 11990 package.insert("repository".to_string(), toml::Value::Table(repository)); 11991 assert!(package_field_configured(&package, "repository")); 11992 } 11993 11994 #[test] 11995 fn validate_required_coverage_summary_enforces_required_threshold() { 11996 let root = temp_root("coverage_summary"); 11997 let coverage_dir = root.join("target").join("coverage"); 11998 fs::create_dir_all(&coverage_dir).expect("create coverage dir"); 11999 fs::write( 12000 coverage_dir.join("coverage-refresh.tsv"), 12001 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_core\tpass\t90.0\t90.0\t90.0\t90.0\tfile\n", 12002 ) 12003 .expect("write coverage file"); 12004 let required = ["radroots_core".to_string()] 12005 .into_iter() 12006 .collect::<BTreeSet<_>>(); 12007 validate_required_coverage_summary(&root, &required, required_thresholds()) 12008 .expect("coverage summary"); 12009 12010 fs::write( 12011 coverage_dir.join("coverage-refresh.tsv"), 12012 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_core\tpass\t90.0\t89.9\t90.0\t90.0\tfile\n", 12013 ) 12014 .expect("write function coverage file"); 12015 let func_err = validate_required_coverage_summary(&root, &required, required_thresholds()) 12016 .expect_err("function coverage below 90"); 12017 assert!(func_err.contains("must satisfy coverage policy")); 12018 12019 fs::write( 12020 coverage_dir.join("coverage-refresh.tsv"), 12021 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_core\tpass\t90.0\t90.0\t89.9\t90.0\tfile\n", 12022 ) 12023 .expect("write branch coverage file"); 12024 let branch_err = 12025 validate_required_coverage_summary(&root, &required, required_thresholds()) 12026 .expect_err("branch coverage below 90"); 12027 assert!(branch_err.contains("must satisfy coverage policy")); 12028 12029 fs::write( 12030 coverage_dir.join("coverage-refresh.tsv"), 12031 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_core\tpass\t90.0\t90.0\tunavailable\t90.0\tfile\n", 12032 ) 12033 .expect("write unavailable branch coverage file"); 12034 let missing_branch_err = 12035 validate_required_coverage_summary(&root, &required, required_thresholds()) 12036 .expect_err("branch coverage missing under strict policy"); 12037 assert!(missing_branch_err.contains("unavailable")); 12038 12039 fs::write( 12040 coverage_dir.join("coverage-refresh.tsv"), 12041 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_core\tpass\t90.0\t90.0\t90.0\t89.9\tfile\n", 12042 ) 12043 .expect("write region coverage file"); 12044 let region_err = 12045 validate_required_coverage_summary(&root, &required, required_thresholds()) 12046 .expect_err("region coverage below 90"); 12047 assert!(region_err.contains("must satisfy coverage policy")); 12048 let _ = fs::remove_dir_all(&root); 12049 } 12050 12051 #[test] 12052 fn validate_required_coverage_summary_with_policy_honors_scope_override() { 12053 let root = temp_root("coverage_summary_override"); 12054 write_test_coverage_refresh( 12055 &root, 12056 &[ 12057 TestCoverageRefreshRow { 12058 crate_name: "radroots_event_codec", 12059 status: "pass", 12060 thresholds: CoverageThresholds { 12061 fail_under_exec_lines: 100.0, 12062 fail_under_functions: 100.0, 12063 fail_under_regions: 99.946, 12064 fail_under_branches: 100.0, 12065 require_branches: true, 12066 }, 12067 exec: 100.0, 12068 func: 100.0, 12069 branch: Some(100.0), 12070 region: 99.946385, 12071 report_pass: true, 12072 }, 12073 TestCoverageRefreshRow { 12074 crate_name: "radroots_log", 12075 status: "pass", 12076 thresholds: coverage_thresholds(100.0, false), 12077 exec: 100.0, 12078 func: 100.0, 12079 branch: None, 12080 region: 100.0, 12081 report_pass: true, 12082 }, 12083 ], 12084 ); 12085 let policy_dir = root.join("contracts"); 12086 fs::create_dir_all(&policy_dir).expect("create policy dir"); 12087 fs::write( 12088 policy_dir.join("coverage.toml"), 12089 "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[overrides.radroots_event_codec]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 99.946\nfail_under_branches = 100.0\ntemporary = true\nreason = \"publish 0.1.0-alpha temporary coverage override\"\n\n[overrides.radroots_log]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = false\ntemporary = true\nreason = \"branch coverage is not applicable while the crate has no measured branch records\"\n\n[required]\ncrates = [\"radroots_event_codec\", \"radroots_log\"]\n", 12090 ) 12091 .expect("write coverage policy"); 12092 let required = [ 12093 "radroots_event_codec".to_string(), 12094 "radroots_log".to_string(), 12095 ] 12096 .into_iter() 12097 .collect::<BTreeSet<_>>(); 12098 let policy = read_coverage_policy(&policy_dir.join("coverage.toml")) 12099 .expect("parse override coverage policy"); 12100 validate_required_coverage_summary_with_policy(&root, &required, &policy) 12101 .expect("coverage summary should honor override"); 12102 let _ = fs::remove_dir_all(&root); 12103 } 12104 12105 #[test] 12106 fn validate_required_coverage_summary_with_policy_rejects_synthetic_report_path() { 12107 let root = temp_root("coverage_summary_synthetic_report_path"); 12108 write_file( 12109 &root 12110 .join("target") 12111 .join("coverage") 12112 .join("coverage-refresh.tsv"), 12113 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100.0\t100.0\t100.0\t100.0\tfile\n", 12114 ); 12115 let required = ["radroots_a".to_string()] 12116 .into_iter() 12117 .collect::<BTreeSet<_>>(); 12118 let policy_dir = root.join("contracts"); 12119 write_file( 12120 &policy_dir.join("coverage.toml"), 12121 "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", 12122 ); 12123 let policy = 12124 read_coverage_policy(&policy_dir.join("coverage.toml")).expect("parse coverage policy"); 12125 let err = validate_required_coverage_summary_with_policy(&root, &required, &policy) 12126 .expect_err("synthetic report path should fail"); 12127 assert!(err.contains("coverage gate report")); 12128 let _ = fs::remove_dir_all(&root); 12129 } 12130 12131 #[test] 12132 fn validate_required_coverage_summary_with_policy_rejects_stale_gate_report_thresholds() { 12133 let root = temp_root("coverage_summary_stale_gate_report_thresholds"); 12134 let row = TestCoverageRefreshRow { 12135 crate_name: "radroots_a", 12136 status: "pass", 12137 thresholds: coverage_thresholds(90.0, true), 12138 exec: 100.0, 12139 func: 100.0, 12140 branch: Some(100.0), 12141 region: 100.0, 12142 report_pass: true, 12143 }; 12144 write_test_coverage_refresh(&root, &[row]); 12145 let required = ["radroots_a".to_string()] 12146 .into_iter() 12147 .collect::<BTreeSet<_>>(); 12148 let policy_dir = root.join("contracts"); 12149 write_file( 12150 &policy_dir.join("coverage.toml"), 12151 "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", 12152 ); 12153 let policy = 12154 read_coverage_policy(&policy_dir.join("coverage.toml")).expect("parse coverage policy"); 12155 let err = validate_required_coverage_summary_with_policy(&root, &required, &policy) 12156 .expect_err("stale threshold report should fail"); 12157 assert!(err.contains("thresholds do not match policy")); 12158 let _ = fs::remove_dir_all(&root); 12159 } 12160 12161 #[test] 12162 fn validate_required_coverage_summary_with_policy_rejects_row_report_mismatch() { 12163 let root = temp_root("coverage_summary_row_report_mismatch"); 12164 let row = TestCoverageRefreshRow { 12165 crate_name: "radroots_a", 12166 status: "pass", 12167 thresholds: coverage_thresholds(100.0, true), 12168 exec: 99.0, 12169 func: 100.0, 12170 branch: Some(100.0), 12171 region: 100.0, 12172 report_pass: true, 12173 }; 12174 let report_relative = write_test_coverage_gate_report(&root, &row); 12175 write_file( 12176 &root 12177 .join("target") 12178 .join("coverage") 12179 .join("coverage-refresh.tsv"), 12180 &format!( 12181 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100.0\t100.0\t100.0\t100.0\t{report_relative}\n" 12182 ), 12183 ); 12184 let required = ["radroots_a".to_string()] 12185 .into_iter() 12186 .collect::<BTreeSet<_>>(); 12187 let policy_dir = root.join("contracts"); 12188 write_file( 12189 &policy_dir.join("coverage.toml"), 12190 "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\"]\n", 12191 ); 12192 let policy = 12193 read_coverage_policy(&policy_dir.join("coverage.toml")).expect("parse coverage policy"); 12194 let err = validate_required_coverage_summary_with_policy(&root, &required, &policy) 12195 .expect_err("row and report mismatch should fail"); 12196 assert!(err.contains("does not match coverage gate report")); 12197 let _ = fs::remove_dir_all(&root); 12198 } 12199 12200 #[test] 12201 fn validate_publish_package_metadata_requires_description() { 12202 let root = temp_root("publish_metadata"); 12203 fs::create_dir_all(root.join("crates").join("a")).expect("create crate dir"); 12204 fs::write( 12205 root.join("Cargo.toml"), 12206 r#"[workspace] 12207 members = ["crates/a"] 12208 "#, 12209 ) 12210 .expect("write workspace manifest"); 12211 fs::write( 12212 root.join("crates").join("a").join("Cargo.toml"), 12213 r#"[package] 12214 name = "radroots_a" 12215 version = "1.0.0" 12216 edition = "2024" 12217 repository = { workspace = true } 12218 homepage = { workspace = true } 12219 documentation = "https://docs.rs/radroots_a" 12220 readme = { workspace = true } 12221 "#, 12222 ) 12223 .expect("write package manifest"); 12224 let publish = ["radroots_a".to_string()] 12225 .into_iter() 12226 .collect::<BTreeSet<_>>(); 12227 let err = 12228 validate_publish_package_metadata(&root, &publish).expect_err("missing description"); 12229 assert!(err.contains("package.description")); 12230 let _ = fs::remove_dir_all(&root); 12231 } 12232 12233 #[test] 12234 fn synthetic_workspace_validates_contract_and_release_preflight() { 12235 let root = create_synthetic_workspace("synthetic_valid"); 12236 let bundle = load_contract_bundle(&root).expect("load synthetic bundle"); 12237 validate_generic_contract_bundle(&bundle).expect("validate synthetic bundle"); 12238 validate_generic_release_preflight(&root).expect("validate synthetic preflight"); 12239 let _ = fs::remove_dir_all(root); 12240 } 12241 12242 #[test] 12243 fn helper_functions_cover_error_paths() { 12244 let empty = collect_unique_set(&["".to_string()], "field").expect_err("empty value"); 12245 assert!(empty.contains("field contains an empty crate name")); 12246 let duplicate = collect_unique_set(&["a".to_string(), "a".to_string()], "field") 12247 .expect_err("duplicate value"); 12248 assert!(duplicate.contains("field has duplicate crate a")); 12249 12250 let values = ["b".to_string(), "a".to_string()]; 12251 let set = collect_unique_set(&values, "field").expect("unique values"); 12252 assert_eq!(join_set(&set), "a, b".to_string()); 12253 12254 assert!(package_publish_enabled(None)); 12255 assert!(package_publish_enabled(Some(&PackagePublish::Bool(true)))); 12256 assert!(!package_publish_enabled(Some(&PackagePublish::Bool(false)))); 12257 assert!(package_publish_enabled(Some(&PackagePublish::Registries( 12258 vec!["crates-io".to_string(),] 12259 )))); 12260 assert!(!package_publish_enabled(Some(&PackagePublish::Registries( 12261 Vec::new() 12262 )))); 12263 12264 let mut package = toml::value::Table::new(); 12265 package.insert("description".to_string(), toml::Value::Integer(42)); 12266 assert!(!package_field_configured(&package, "description")); 12267 12268 assert!(!publish_config_is_public(None)); 12269 assert!(!publish_config_is_public(Some(&PackagePublish::Bool(true)))); 12270 assert!(publish_config_is_public(Some(&PackagePublish::Registries( 12271 vec!["crates-io".to_string(),] 12272 )))); 12273 assert!(!publish_config_is_public(Some( 12274 &PackagePublish::Registries(vec!["crates-io".to_string(), "mirror".to_string(),]) 12275 ))); 12276 assert!(!publish_config_is_public(Some( 12277 &PackagePublish::Registries(vec!["mirror".to_string(),]) 12278 ))); 12279 12280 assert!(!publish_config_is_non_public(None)); 12281 assert!(!publish_config_is_non_public(Some(&PackagePublish::Bool( 12282 true 12283 )))); 12284 assert!(publish_config_is_non_public(Some(&PackagePublish::Bool( 12285 false 12286 )))); 12287 assert!(!publish_config_is_non_public(Some( 12288 &PackagePublish::Registries(vec!["crates-io".to_string(),]) 12289 ))); 12290 } 12291 12292 #[test] 12293 fn release_contract_helpers_cover_classification_and_env_override_paths() { 12294 let release = ReleaseSection { 12295 version: "1.0.0".to_string(), 12296 }; 12297 let empty_order = ReleaseCrateSet { crates: Vec::new() }; 12298 12299 let legacy = ReleaseContractFile { 12300 release: ReleaseSection { 12301 version: release.version.clone(), 12302 }, 12303 publication: None, 12304 workspace_classification: None, 12305 classification: ReleaseClassification::default(), 12306 publish: Some(ReleaseCrateSet { 12307 crates: vec!["radroots_public".to_string()], 12308 }), 12309 internal: Some(ReleaseCrateSet { 12310 crates: vec!["radroots_internal".to_string()], 12311 }), 12312 publish_order: ReleaseCrateSet { 12313 crates: empty_order.crates.clone(), 12314 }, 12315 }; 12316 assert!(!legacy.uses_classification()); 12317 assert_eq!(legacy.public_crates(), vec!["radroots_public".to_string()]); 12318 assert_eq!( 12319 legacy.internal_crates(), 12320 vec!["radroots_internal".to_string()] 12321 ); 12322 12323 let empty_legacy = ReleaseContractFile { 12324 release: ReleaseSection { 12325 version: release.version.clone(), 12326 }, 12327 publication: None, 12328 workspace_classification: None, 12329 classification: ReleaseClassification::default(), 12330 publish: None, 12331 internal: None, 12332 publish_order: ReleaseCrateSet { 12333 crates: empty_order.crates.clone(), 12334 }, 12335 }; 12336 assert!(!empty_legacy.uses_classification()); 12337 assert_eq!(empty_legacy.public_crates(), Vec::<String>::new()); 12338 assert_eq!(empty_legacy.internal_crates(), Vec::<String>::new()); 12339 12340 let internal = ReleaseContractFile { 12341 release: ReleaseSection { 12342 version: release.version.clone(), 12343 }, 12344 publication: None, 12345 workspace_classification: None, 12346 classification: ReleaseClassification { 12347 internal: vec!["radroots_internal_only".to_string()], 12348 ..ReleaseClassification::default() 12349 }, 12350 publish: None, 12351 internal: None, 12352 publish_order: ReleaseCrateSet { 12353 crates: empty_order.crates.clone(), 12354 }, 12355 }; 12356 assert!(internal.uses_classification()); 12357 12358 let deferred = ReleaseContractFile { 12359 release: ReleaseSection { 12360 version: release.version.clone(), 12361 }, 12362 publication: None, 12363 workspace_classification: None, 12364 classification: ReleaseClassification { 12365 deferred: vec!["radroots_deferred".to_string()], 12366 ..ReleaseClassification::default() 12367 }, 12368 publish: None, 12369 internal: None, 12370 publish_order: ReleaseCrateSet { 12371 crates: empty_order.crates.clone(), 12372 }, 12373 }; 12374 assert!(deferred.uses_classification()); 12375 assert_eq!( 12376 deferred.deferred_crates(), 12377 vec!["radroots_deferred".to_string()] 12378 ); 12379 12380 let retired = ReleaseContractFile { 12381 release: ReleaseSection { 12382 version: release.version.clone(), 12383 }, 12384 publication: None, 12385 workspace_classification: None, 12386 classification: ReleaseClassification { 12387 retired: vec!["radroots_retired".to_string()], 12388 ..ReleaseClassification::default() 12389 }, 12390 publish: None, 12391 internal: None, 12392 publish_order: ReleaseCrateSet { 12393 crates: empty_order.crates.clone(), 12394 }, 12395 }; 12396 assert!(retired.uses_classification()); 12397 assert_eq!( 12398 retired.retired_crates(), 12399 vec!["radroots_retired".to_string()] 12400 ); 12401 12402 let yank_only = ReleaseContractFile { 12403 release, 12404 publication: None, 12405 workspace_classification: None, 12406 classification: ReleaseClassification { 12407 yank_only: vec!["radroots_yank_only".to_string()], 12408 ..ReleaseClassification::default() 12409 }, 12410 publish: None, 12411 internal: None, 12412 publish_order: empty_order, 12413 }; 12414 assert!(yank_only.uses_classification()); 12415 assert_eq!( 12416 yank_only.yank_only_crates(), 12417 vec!["radroots_yank_only".to_string()] 12418 ); 12419 12420 let root = create_synthetic_workspace("release_contract_env_override"); 12421 let policy_path = root_release_policy_path(&root); 12422 let resolved = 12423 resolve_release_contract_path_with_override(&root, "1.0.0", Some(policy_path.clone())) 12424 .expect("existing override policy should resolve"); 12425 assert_eq!(resolved, policy_path); 12426 12427 let missing_policy = root.join("missing-release-policy.toml"); 12428 let err = resolve_release_contract_path_with_override( 12429 &root, 12430 "1.0.0", 12431 Some(missing_policy.clone()), 12432 ) 12433 .expect_err("missing fixture policy should fail"); 12434 assert!(err.contains("release policy override points to a missing file")); 12435 assert!(err.contains(&missing_policy.display().to_string())); 12436 12437 let _ = fs::remove_dir_all(&root); 12438 } 12439 12440 #[test] 12441 fn workspace_package_manifests_reject_duplicate_package_names() { 12442 let root = temp_root("workspace_manifest_duplicates"); 12443 write_file( 12444 &root.join("Cargo.toml"), 12445 r#"[workspace] 12446 members = ["crates/a", "crates/b"] 12447 "#, 12448 ); 12449 let package_manifest = 12450 "[package]\nname = \"duplicate\"\nversion = \"0.1.0\"\nedition = \"2024\"\n"; 12451 write_file( 12452 &root.join("crates").join("a").join("Cargo.toml"), 12453 package_manifest, 12454 ); 12455 write_file( 12456 &root.join("crates").join("b").join("Cargo.toml"), 12457 package_manifest, 12458 ); 12459 let err = workspace_package_manifests(&root) 12460 .expect_err("duplicate package names in manifest map"); 12461 assert!(err.contains("duplicate workspace package name in manifest map")); 12462 let _ = fs::remove_dir_all(root); 12463 } 12464 12465 #[test] 12466 fn coverage_refresh_parsing_and_summary_errors_are_reported() { 12467 let root = temp_root("coverage_refresh_errors"); 12468 let coverage_dir = root.join("target").join("coverage"); 12469 fs::create_dir_all(&coverage_dir).expect("create coverage dir"); 12470 12471 write_file( 12472 &coverage_dir.join("coverage-refresh.tsv"), 12473 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nbad-row\n", 12474 ); 12475 let bad_row = load_coverage_refresh_rows(&root).expect_err("invalid coverage row"); 12476 assert!(bad_row.contains("at least 7 columns")); 12477 12478 write_file( 12479 &coverage_dir.join("coverage-refresh.tsv"), 12480 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\tnot-a-number\t100\t100\t100\tfile\n", 12481 ); 12482 let bad_percent = load_coverage_refresh_rows(&root).expect_err("invalid coverage percent"); 12483 assert!(bad_percent.contains("parse exec")); 12484 12485 write_file( 12486 &coverage_dir.join("coverage-refresh.tsv"), 12487 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100\t100\t100\tnot-a-number\tfile\n", 12488 ); 12489 let bad_region = 12490 load_coverage_refresh_rows(&root).expect_err("invalid region coverage percent"); 12491 assert!(bad_region.contains("parse region")); 12492 12493 write_file( 12494 &coverage_dir.join("coverage-refresh.tsv"), 12495 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100\t100\t100\t100\tfile\nradroots_a\tpass\t100\t100\t100\t100\tfile\n", 12496 ); 12497 let duplicate_row = load_coverage_refresh_rows(&root).expect_err("duplicate coverage row"); 12498 assert!(duplicate_row.contains("duplicate coverage row")); 12499 12500 write_file( 12501 &coverage_dir.join("coverage-refresh.tsv"), 12502 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tfail\t100\t100\t100\t100\tfile\n", 12503 ); 12504 let required = ["radroots_a".to_string()] 12505 .into_iter() 12506 .collect::<BTreeSet<_>>(); 12507 let non_pass = validate_required_coverage_summary(&root, &required, required_thresholds()) 12508 .expect_err("non-pass status"); 12509 assert!(non_pass.contains("non-pass status")); 12510 12511 write_file( 12512 &coverage_dir.join("coverage-refresh.tsv"), 12513 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t89.9\t90\t90\t90\tfile\n", 12514 ); 12515 let below_90 = validate_required_coverage_summary(&root, &required, required_thresholds()) 12516 .expect_err("coverage below 90"); 12517 assert!(below_90.contains("must satisfy coverage policy")); 12518 12519 let missing = ["missing".to_string()].into_iter().collect::<BTreeSet<_>>(); 12520 let missing_err = 12521 validate_required_coverage_summary(&root, &missing, required_thresholds()) 12522 .expect_err("missing required row"); 12523 assert!(missing_err.contains("missing from coverage-refresh.tsv")); 12524 12525 let _ = fs::remove_dir_all(root); 12526 } 12527 12528 #[test] 12529 fn enum_extract_and_parse_error_paths_are_reported() { 12530 let missing = 12531 extract_enum_body("pub struct X;", "UnitDimension").expect_err("missing enum"); 12532 assert!(missing.contains("missing enum")); 12533 12534 let missing_brace = extract_enum_body("pub enum UnitDimension", "UnitDimension") 12535 .expect_err("missing opening brace"); 12536 assert!(missing_brace.contains("missing opening brace")); 12537 12538 let missing_close = 12539 extract_enum_body("pub enum UnitDimension { Count, Mass", "UnitDimension") 12540 .expect_err("missing closing brace"); 12541 assert!(missing_close.contains("missing closing brace")); 12542 12543 let variants = parse_enum_variants( 12544 r#" 12545 , 12546 = 1, 12547 // skip 12548 #![cfg(test)] 12549 Count, 12550 "#, 12551 ); 12552 assert_eq!(variants, vec!["Count".to_string()]); 12553 12554 let nested = extract_enum_body( 12555 "pub enum UnitDimension { Count = { 1 }, Mass = 2 }", 12556 "UnitDimension", 12557 ) 12558 .expect("nested braces in enum body"); 12559 assert!(nested.contains("Count")); 12560 } 12561 12562 #[test] 12563 fn coverage_policy_parity_reports_contract_errors() { 12564 let root = create_synthetic_workspace("coverage_policy_errors"); 12565 let contract_root = root.join("contracts"); 12566 let coverage_root = coverage_root(&contract_root); 12567 12568 write_file( 12569 &coverage_root.join("coverage.toml"), 12570 r#"[gate] 12571 fail_under_exec_lines = 90.0 12572 fail_under_functions = 90.0 12573 fail_under_regions = 90.0 12574 fail_under_branches = 90.0 12575 require_branches = true 12576 12577 [required] 12578 crates = [] 12579 "#, 12580 ); 12581 let empty_required = 12582 validate_coverage_policy_parity(&root, &contract_root).expect_err("empty required"); 12583 assert!(empty_required.contains("required crates list must not be empty")); 12584 12585 write_file( 12586 &coverage_root.join("coverage.toml"), 12587 r#"[gate] 12588 fail_under_exec_lines = 89.0 12589 fail_under_functions = 90.0 12590 fail_under_regions = 90.0 12591 fail_under_branches = 90.0 12592 require_branches = true 12593 12594 [required] 12595 crates = ["radroots_a", "radroots_b"] 12596 "#, 12597 ); 12598 let invalid_gate = validate_coverage_policy_parity(&root, &contract_root) 12599 .expect_err("invalid policy thresholds"); 12600 assert!(invalid_gate.contains("90/90/90/90")); 12601 12602 write_file( 12603 &coverage_root.join("coverage.toml"), 12604 r#"[gate] 12605 fail_under_exec_lines = 90.0 12606 fail_under_functions = 89.0 12607 fail_under_regions = 90.0 12608 fail_under_branches = 90.0 12609 require_branches = true 12610 12611 [required] 12612 crates = ["radroots_a", "radroots_b"] 12613 "#, 12614 ); 12615 let invalid_functions = validate_coverage_policy_parity(&root, &contract_root) 12616 .expect_err("invalid function threshold"); 12617 assert!(invalid_functions.contains("90/90/90/90")); 12618 12619 write_file( 12620 &coverage_root.join("coverage.toml"), 12621 r#"[gate] 12622 fail_under_exec_lines = 90.0 12623 fail_under_functions = 90.0 12624 fail_under_regions = 89.0 12625 fail_under_branches = 90.0 12626 require_branches = true 12627 12628 [required] 12629 crates = ["radroots_a", "radroots_b"] 12630 "#, 12631 ); 12632 let invalid_regions = validate_coverage_policy_parity(&root, &contract_root) 12633 .expect_err("invalid region threshold"); 12634 assert!(invalid_regions.contains("90/90/90/90")); 12635 12636 write_file( 12637 &coverage_root.join("coverage.toml"), 12638 r#"[gate] 12639 fail_under_exec_lines = 90.0 12640 fail_under_functions = 90.0 12641 fail_under_regions = 90.0 12642 fail_under_branches = 89.0 12643 require_branches = true 12644 12645 [required] 12646 crates = ["radroots_a", "radroots_b"] 12647 "#, 12648 ); 12649 let invalid_branches = validate_coverage_policy_parity(&root, &contract_root) 12650 .expect_err("invalid branch threshold"); 12651 assert!(invalid_branches.contains("90/90/90/90")); 12652 12653 write_file( 12654 &coverage_root.join("coverage.toml"), 12655 r#"[gate] 12656 fail_under_exec_lines = 90.0 12657 fail_under_functions = 90.0 12658 fail_under_regions = 90.0 12659 fail_under_branches = 90.0 12660 require_branches = true 12661 12662 [required] 12663 crates = ["radroots_a", "radroots_a"] 12664 "#, 12665 ); 12666 let duplicate_required = validate_coverage_policy_parity(&root, &contract_root) 12667 .expect_err("duplicate required crate"); 12668 assert!(duplicate_required.contains("duplicate crate")); 12669 12670 write_file( 12671 &coverage_root.join("coverage.toml"), 12672 r#"[gate] 12673 fail_under_exec_lines = 90.0 12674 fail_under_functions = 90.0 12675 fail_under_regions = 90.0 12676 fail_under_branches = 90.0 12677 require_branches = false 12678 12679 [required] 12680 crates = ["radroots_a", "radroots_b"] 12681 "#, 12682 ); 12683 let branches_optional = validate_coverage_policy_parity(&root, &contract_root) 12684 .expect_err("branches must be required"); 12685 assert!(branches_optional.contains("required branches")); 12686 12687 write_file( 12688 &coverage_root.join("coverage.toml"), 12689 r#"[gate] 12690 fail_under_exec_lines = 90.0 12691 fail_under_functions = 90.0 12692 fail_under_regions = 90.0 12693 fail_under_branches = 90.0 12694 require_branches = true 12695 12696 [overrides.radroots_a] 12697 fail_under_exec_lines = 89.9 12698 temporary = true 12699 reason = "invalid override below the active development baseline" 12700 12701 [required] 12702 crates = ["radroots_a", "radroots_b"] 12703 "#, 12704 ); 12705 let below_minimum_override = validate_coverage_policy_parity(&root, &contract_root) 12706 .expect_err("numeric override below the active baseline"); 12707 assert!(below_minimum_override.contains("scope radroots_a")); 12708 assert!(below_minimum_override.contains("at least 90/90/90/90")); 12709 12710 write_file( 12711 &coverage_root.join("coverage.toml"), 12712 r#"[gate] 12713 fail_under_exec_lines = 90.0 12714 fail_under_functions = 90.0 12715 fail_under_regions = 90.0 12716 fail_under_branches = 90.0 12717 require_branches = true 12718 12719 [required] 12720 crates = ["radroots_b"] 12721 "#, 12722 ); 12723 let missing_workspace = validate_coverage_policy_parity(&root, &contract_root) 12724 .expect_err("missing workspace crate in policy"); 12725 assert!(missing_workspace.contains("missing workspace crates")); 12726 12727 write_file( 12728 &coverage_root.join("coverage.toml"), 12729 r#"[gate] 12730 fail_under_exec_lines = 90.0 12731 fail_under_functions = 90.0 12732 fail_under_regions = 90.0 12733 fail_under_branches = 90.0 12734 require_branches = true 12735 12736 [required] 12737 crates = ["unknown"] 12738 "#, 12739 ); 12740 let required_unknown = validate_coverage_policy_parity(&root, &contract_root) 12741 .expect_err("unknown required crate"); 12742 assert!(required_unknown.contains("includes excluded or unknown crates")); 12743 12744 let _ = fs::remove_dir_all(root); 12745 } 12746 12747 #[test] 12748 fn release_publish_policy_reports_contract_errors() { 12749 let root = create_synthetic_workspace("release_policy_errors"); 12750 let contract_root = root.join("contracts"); 12751 let release_policy_path = root_release_policy_path(&root); 12752 12753 write_file( 12754 &release_policy_path, 12755 r#"[release] 12756 version = "" 12757 12758 [publish] 12759 crates = ["radroots_a"] 12760 12761 [internal] 12762 crates = ["radroots_b"] 12763 12764 [publish_order] 12765 crates = ["radroots_a"] 12766 "#, 12767 ); 12768 let empty_version = validate_release_publish_policy(&root, &contract_root, "1.0.0") 12769 .expect_err("empty release version"); 12770 assert!(empty_version.contains("must not be empty")); 12771 12772 write_file( 12773 &release_policy_path, 12774 r#"[release] 12775 version = "2.0.0" 12776 12777 [publish] 12778 crates = ["radroots_a"] 12779 12780 [internal] 12781 crates = ["radroots_b"] 12782 12783 [publish_order] 12784 crates = ["radroots_a"] 12785 "#, 12786 ); 12787 let version_mismatch = validate_release_publish_policy(&root, &contract_root, "1.0.0") 12788 .expect_err("release version mismatch"); 12789 assert!(version_mismatch.contains("must match contract version")); 12790 12791 write_file( 12792 &release_policy_path, 12793 r#"[release] 12794 version = "1.0.0" 12795 12796 [publish] 12797 crates = ["radroots_a"] 12798 12799 [internal] 12800 crates = ["radroots_a"] 12801 12802 [publish_order] 12803 crates = ["radroots_a"] 12804 "#, 12805 ); 12806 let overlap = validate_release_publish_policy(&root, &contract_root, "1.0.0") 12807 .expect_err("publish/internal overlap"); 12808 assert!(overlap.contains("overlap is not allowed")); 12809 12810 write_file( 12811 &release_policy_path, 12812 r#"[release] 12813 version = "1.0.0" 12814 12815 [publish] 12816 crates = ["radroots_a"] 12817 12818 [internal] 12819 crates = [] 12820 12821 [publish_order] 12822 crates = ["radroots_a"] 12823 "#, 12824 ); 12825 let missing_workspace = validate_release_publish_policy(&root, &contract_root, "1.0.0") 12826 .expect_err("missing workspace crate"); 12827 assert!(missing_workspace.contains("missing workspace crates")); 12828 12829 write_file( 12830 &release_policy_path, 12831 r#"[release] 12832 version = "1.0.0" 12833 12834 [publish] 12835 crates = ["radroots_a"] 12836 12837 [internal] 12838 crates = ["radroots_b"] 12839 12840 [publish_order] 12841 crates = [] 12842 "#, 12843 ); 12844 let missing_publish_order = validate_release_publish_policy(&root, &contract_root, "1.0.0") 12845 .expect_err("missing publish order entries"); 12846 assert!(missing_publish_order.contains("missing publish crates")); 12847 12848 write_file( 12849 &release_policy_path, 12850 r#"[release] 12851 version = "1.0.0" 12852 12853 [publish] 12854 crates = ["radroots_a"] 12855 12856 [internal] 12857 crates = ["radroots_b"] 12858 12859 [publish_order] 12860 crates = ["radroots_a", "radroots_b"] 12861 "#, 12862 ); 12863 let extra_publish_order = validate_release_publish_policy(&root, &contract_root, "1.0.0") 12864 .expect_err("extra publish order entries"); 12865 assert!(extra_publish_order.contains("non-publish crates")); 12866 12867 write_file( 12868 &root.join("crates").join("a").join("Cargo.toml"), 12869 r#"[package] 12870 name = "radroots_a" 12871 publish = ["crates-io"] 12872 version = "1.0.0" 12873 edition = "2024" 12874 authors = ["Radroots Test"] 12875 rust-version = "1.97" 12876 license = "MIT OR Apache-2.0" 12877 description = "crate a" 12878 repository = "https://example.com/a" 12879 homepage = "https://example.com/a" 12880 documentation = "https://docs.rs/radroots_a" 12881 readme = "README.md" 12882 keywords = ["radroots"] 12883 categories = ["data-structures"] 12884 include = ["src/**", "tests/**", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 12885 12886 [package.metadata.docs.rs] 12887 features = [] 12888 12889 [dependencies] 12890 radroots_b = { path = "../b" } 12891 "#, 12892 ); 12893 write_file( 12894 &root.join("crates").join("b").join("Cargo.toml"), 12895 r#"[package] 12896 name = "radroots_b" 12897 version = "1.0.0" 12898 edition = "2024" 12899 description = "crate b" 12900 repository = "https://example.com/b" 12901 homepage = "https://example.com/b" 12902 documentation = "https://docs.example.com/b" 12903 readme = "README" 12904 "#, 12905 ); 12906 write_file( 12907 &release_policy_path, 12908 r#"[release] 12909 version = "1.0.0" 12910 12911 [publish] 12912 crates = ["radroots_a", "radroots_b"] 12913 12914 [internal] 12915 crates = [] 12916 12917 [publish_order] 12918 crates = ["radroots_a", "radroots_b"] 12919 "#, 12920 ); 12921 let dependency_order = validate_release_publish_policy(&root, &contract_root, "1.0.0") 12922 .expect_err("dependency order violation"); 12923 assert!(dependency_order.contains("must place dependency")); 12924 12925 write_file( 12926 &release_policy_path, 12927 r#"[release] 12928 version = "1.0.0" 12929 12930 [publish] 12931 crates = ["radroots_a"] 12932 12933 [internal] 12934 crates = ["radroots_b"] 12935 12936 [publish_order] 12937 crates = ["radroots_a"] 12938 "#, 12939 ); 12940 write_file( 12941 &root.join("crates").join("b").join("Cargo.toml"), 12942 r#"[package] 12943 name = "radroots_b" 12944 version = "1.0.0" 12945 edition = "2024" 12946 publish = false 12947 "#, 12948 ); 12949 validate_release_publish_policy(&root, &contract_root, "1.0.0") 12950 .expect("internal dependency should be ignored in publish ordering"); 12951 12952 write_file( 12953 &root.join("crates").join("a").join("Cargo.toml"), 12954 r#"[package] 12955 name = "radroots_a" 12956 version = "1.0.0" 12957 edition = "2024" 12958 publish = false 12959 "#, 12960 ); 12961 write_file( 12962 &release_policy_path, 12963 r#"[release] 12964 version = "1.0.0" 12965 12966 [publish] 12967 crates = ["radroots_a"] 12968 12969 [internal] 12970 crates = ["radroots_b"] 12971 12972 [publish_order] 12973 crates = ["radroots_a"] 12974 "#, 12975 ); 12976 let publish_flag = validate_release_publish_policy(&root, &contract_root, "1.0.0") 12977 .expect_err("publish crate must be publishable"); 12978 assert!(publish_flag.contains("must set publish = [\"crates-io\"]")); 12979 12980 write_file( 12981 &root.join("crates").join("a").join("Cargo.toml"), 12982 r#"[package] 12983 name = "radroots_a" 12984 publish = ["crates-io"] 12985 version = "1.0.0" 12986 edition = "2024" 12987 description = "crate a" 12988 repository = "https://example.com/a" 12989 homepage = "https://example.com/a" 12990 documentation = "https://docs.example.com/a" 12991 readme = "README" 12992 "#, 12993 ); 12994 write_file( 12995 &root.join("crates").join("b").join("Cargo.toml"), 12996 r#"[package] 12997 name = "radroots_b" 12998 version = "1.0.0" 12999 edition = "2024" 13000 "#, 13001 ); 13002 let internal_flag = validate_release_publish_policy(&root, &contract_root, "1.0.0") 13003 .expect_err("internal crate must be non-publishable"); 13004 assert!(internal_flag.contains("non-public crate")); 13005 13006 let _ = fs::remove_dir_all(root); 13007 } 13008 13009 #[test] 13010 fn publication_freeze_requires_every_workspace_package_to_be_private() { 13011 let root = create_synthetic_workspace("publication_freeze"); 13012 let contract_root = root.join("contracts"); 13013 let release_policy_path = root_release_policy_path(&root); 13014 write_file( 13015 &root.join("crates").join("a").join("Cargo.toml"), 13016 r#"[package] 13017 name = "radroots_a" 13018 version = "1.0.0" 13019 edition = "2024" 13020 publish = false 13021 "#, 13022 ); 13023 write_file( 13024 &release_policy_path, 13025 r#"[release] 13026 version = "1.0.0" 13027 13028 [publication] 13029 frozen = true 13030 registry = "crates-io" 13031 final_enablement_step = 305 13032 13033 [publish] 13034 crates = ["radroots_a"] 13035 13036 [internal] 13037 crates = ["radroots_b"] 13038 13039 [publish_order] 13040 crates = ["radroots_a"] 13041 "#, 13042 ); 13043 validate_release_publish_policy_with_override( 13044 &root, 13045 &contract_root, 13046 "1.0.0", 13047 Some(release_policy_path.clone()), 13048 ) 13049 .expect("fully private workspace should satisfy publication freeze"); 13050 13051 write_file( 13052 &root.join("crates").join("a").join("Cargo.toml"), 13053 r#"[package] 13054 name = "radroots_a" 13055 version = "1.0.0" 13056 edition = "2024" 13057 publish = ["crates-io"] 13058 "#, 13059 ); 13060 let publishable = validate_release_publish_policy_with_override( 13061 &root, 13062 &contract_root, 13063 "1.0.0", 13064 Some(release_policy_path.clone()), 13065 ) 13066 .expect_err("publication freeze must reject a publishable package"); 13067 assert!(publishable.contains("publication freeze requires workspace crate radroots_a")); 13068 13069 write_file( 13070 &release_policy_path, 13071 r#"[release] 13072 version = "1.0.0" 13073 13074 [publish] 13075 crates = ["radroots_a"] 13076 13077 [internal] 13078 crates = ["radroots_b"] 13079 13080 [publish_order] 13081 crates = ["radroots_a"] 13082 "#, 13083 ); 13084 let missing_control = validate_release_publish_policy_with_override( 13085 &root, 13086 &contract_root, 13087 "1.0.0", 13088 Some(release_policy_path), 13089 ) 13090 .expect_err("release policy must carry explicit publication control"); 13091 assert!(missing_control.contains("publication control is required")); 13092 13093 let _ = fs::remove_dir_all(root); 13094 } 13095 13096 #[test] 13097 fn v1_release_policy_covers_approved_unapproved_unclassified_and_private_fixtures() { 13098 let root = create_synthetic_workspace("v1_release_policy"); 13099 let contract_root = root.join("contracts"); 13100 let release_policy_path = root_release_policy_path(&root); 13101 for member in ["a", "b"] { 13102 write_file( 13103 &root.join("crates").join(member).join("Cargo.toml"), 13104 &format!( 13105 "[package]\nname = \"radroots_{member}\"\nversion = \"1.0.0\"\nedition = \"2024\"\npublish = false\n" 13106 ), 13107 ); 13108 } 13109 13110 let approved = (1..=19) 13111 .map(|index| format!("package-{index:02}")) 13112 .collect::<Vec<_>>(); 13113 let approved_toml = approved 13114 .iter() 13115 .map(|name| format!("\"{name}\"")) 13116 .collect::<Vec<_>>() 13117 .join(", "); 13118 let external_toml = approved[1..] 13119 .iter() 13120 .map(|name| format!("\"{name}\"")) 13121 .collect::<Vec<_>>() 13122 .join(", "); 13123 let mut architecture = format!( 13124 "spec_id = \"radroots.crates.release.v1\"\npackage_count = 19\n\n[repositories.lib]\nversion = \"0.1.0-alpha\"\npackages = [\"package-01\"]\n\n[repositories.sdk]\nversion = \"0.1.0\"\npackages = [{external_toml}]\n" 13125 ); 13126 for name in &approved { 13127 architecture.push_str(&format!("\n[[package]]\nname = \"{name}\"\n")); 13128 } 13129 write_file( 13130 &root.join("contracts/crates/release_v1/radroots_crates_release_v1.toml"), 13131 &architecture, 13132 ); 13133 13134 let policy = |approved_packages: &str, test_support: &str| { 13135 format!( 13136 r#"[release] 13137 version = "1.0.0" 13138 13139 [publication] 13140 frozen = true 13141 registry = "crates-io" 13142 final_enablement_step = 305 13143 spec_id = "radroots.crates.release.v1" 13144 approved_packages = [{approved_packages}] 13145 local_packages = ["package-01"] 13146 external_packages = [{external_toml}] 13147 13148 [workspace_classification] 13149 private = ["radroots_a"] 13150 build_codegen = [] 13151 test_support = [{test_support}] 13152 preview = [] 13153 retired = [] 13154 13155 [publish_order] 13156 crates = [] 13157 "# 13158 ) 13159 }; 13160 13161 write_file( 13162 &release_policy_path, 13163 &policy(&approved_toml, "\"radroots_b\""), 13164 ); 13165 validate_release_publish_policy(&root, &contract_root, "1.0.0") 13166 .expect("approved and exhaustively classified fixture must pass"); 13167 13168 let unapproved = format!("{approved_toml}, \"unapproved-public\""); 13169 write_file(&release_policy_path, &policy(&unapproved, "\"radroots_b\"")); 13170 let unapproved_error = validate_release_publish_policy(&root, &contract_root, "1.0.0") 13171 .expect_err("unapproved public package must fail"); 13172 assert!(unapproved_error.contains("unapproved packages: unapproved-public")); 13173 13174 write_file(&release_policy_path, &policy(&approved_toml, "")); 13175 let unclassified = validate_release_publish_policy(&root, &contract_root, "1.0.0") 13176 .expect_err("unclassified workspace package must fail"); 13177 assert!(unclassified.contains("workspace classification is missing packages: radroots_b")); 13178 13179 write_file( 13180 &release_policy_path, 13181 &policy(&approved_toml, "\"radroots_b\""), 13182 ); 13183 write_file( 13184 &root.join("crates/a/Cargo.toml"), 13185 "[package]\nname = \"radroots_a\"\nversion = \"1.0.0\"\nedition = \"2024\"\npublish = [\"crates-io\"]\n", 13186 ); 13187 let private = validate_release_publish_policy(&root, &contract_root, "1.0.0") 13188 .expect_err("private package must remain non-publishable"); 13189 assert!(private.contains("publication freeze requires workspace crate radroots_a")); 13190 13191 let _ = fs::remove_dir_all(root); 13192 } 13193 13194 #[test] 13195 fn release_preflight_rejects_public_dto_tooling_git_or_path_sources() { 13196 let root = create_synthetic_workspace("release_policy_dto_tooling_sources"); 13197 13198 write_file( 13199 &root.join("crates").join("a").join("Cargo.toml"), 13200 r#"[package] 13201 name = "radroots_a" 13202 publish = ["crates-io"] 13203 version = "1.0.0" 13204 edition = "2024" 13205 authors = ["Radroots Test"] 13206 rust-version = "1.97" 13207 license = "MIT OR Apache-2.0" 13208 description = "crate a" 13209 repository = "https://example.com/a" 13210 homepage = "https://example.com/a" 13211 documentation = "https://docs.rs/radroots_a" 13212 readme = "README.md" 13213 keywords = ["radroots"] 13214 categories = ["data-structures"] 13215 include = ["src/**", "tests/**", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 13216 13217 [package.metadata.docs.rs] 13218 features = [] 13219 13220 [dependencies] 13221 dto_bindgen_core = { path = "../../dto_bindgen_core", version = "0.1.0", optional = true } 13222 "#, 13223 ); 13224 let path_err = 13225 validate_generic_release_preflight(&root).expect_err("public path DTO dependency"); 13226 assert!(path_err.contains("radroots_a dependencies.dto_bindgen_core")); 13227 assert!(path_err.contains("not a path source")); 13228 13229 write_file( 13230 &root.join("Cargo.toml"), 13231 r#"[workspace] 13232 members = ["crates/a", "crates/b"] 13233 resolver = "2" 13234 13235 [workspace.package] 13236 version = "1.0.0" 13237 13238 [workspace.dependencies] 13239 dto_bindgen = { version = "0.1.0", git = "https://example.com/dto_bindgen", rev = "abc123" } 13240 radroots_a = { path = "crates/a", version = "=1.0.0" } 13241 radroots_b = { path = "crates/b", version = "=1.0.0" } 13242 "#, 13243 ); 13244 write_file( 13245 &root.join("crates").join("a").join("Cargo.toml"), 13246 r#"[package] 13247 name = "radroots_a" 13248 publish = ["crates-io"] 13249 version = "1.0.0" 13250 edition = "2024" 13251 description = "crate a" 13252 repository = "https://example.com/a" 13253 homepage = "https://example.com/a" 13254 documentation = "https://docs.example.com/a" 13255 readme = "README" 13256 13257 [dependencies] 13258 dto_bindgen = { workspace = true, optional = true } 13259 "#, 13260 ); 13261 let git_err = validate_generic_release_preflight(&root) 13262 .expect_err("public workspace git DTO dependency"); 13263 assert!(git_err.contains("radroots_a dependencies.dto_bindgen")); 13264 assert!(git_err.contains("not a git source")); 13265 13266 write_file( 13267 &root.join("crates").join("a").join("Cargo.toml"), 13268 r#"[package] 13269 name = "radroots_a" 13270 publish = ["crates-io"] 13271 version = "1.0.0" 13272 edition = "2024" 13273 authors = ["Radroots Test"] 13274 rust-version = "1.97" 13275 license = "MIT OR Apache-2.0" 13276 description = "crate a" 13277 repository = "https://example.com/a" 13278 homepage = "https://example.com/a" 13279 documentation = "https://docs.rs/radroots_a" 13280 readme = "README.md" 13281 keywords = ["radroots"] 13282 categories = ["data-structures"] 13283 include = ["src/**", "tests/**", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] 13284 13285 [package.metadata.docs.rs] 13286 features = [] 13287 "#, 13288 ); 13289 write_file( 13290 &root.join("crates").join("b").join("Cargo.toml"), 13291 r#"[package] 13292 name = "radroots_b" 13293 version = "1.0.0" 13294 edition = "2024" 13295 publish = false 13296 13297 [dependencies] 13298 dto_bindgen = { workspace = true, optional = true } 13299 13300 [features] 13301 default = ["std"] 13302 std = [] 13303 legacy-ingest = ["std"] 13304 "#, 13305 ); 13306 validate_generic_release_preflight(&root) 13307 .expect("internal DTO tooling source does not block public publish policy"); 13308 13309 let _ = fs::remove_dir_all(root); 13310 } 13311 13312 #[test] 13313 fn validate_contract_bundle_reports_required_field_errors() { 13314 let root = create_synthetic_workspace("contract_bundle_errors"); 13315 13316 let assert_bundle_error = |expected: &str, mutator: fn(&mut ContractBundle)| { 13317 let mut bundle = load_contract_bundle(&root).expect("load bundle"); 13318 mutator(&mut bundle); 13319 let err = match validate_generic_contract_bundle(&bundle) { 13320 Ok(()) => panic!("expected bundle validation error: {expected}"), 13321 Err(err) => err, 13322 }; 13323 assert!(err.contains(expected), "expected `{expected}` in `{err}`"); 13324 }; 13325 13326 assert_bundle_error("contract name is required", |bundle| { 13327 bundle.manifest.contract.name.clear(); 13328 }); 13329 assert_bundle_error("contract version is required", |bundle| { 13330 bundle.manifest.contract.version.clear(); 13331 }); 13332 assert_bundle_error("contract source is required", |bundle| { 13333 bundle.manifest.contract.source.clear(); 13334 }); 13335 assert_bundle_error("surface.model_crates must not be empty", |bundle| { 13336 bundle.manifest.surface.model_crates.clear(); 13337 }); 13338 assert_bundle_error("surface.algorithm_crates must not be empty", |bundle| { 13339 bundle.manifest.surface.algorithm_crates.clear(); 13340 }); 13341 assert_bundle_error( 13342 "surface.internal_replica_crates.storage must be a crate identifier", 13343 |bundle| { 13344 bundle.manifest.surface.internal_replica_crates = Some(InternalReplicaCrates { 13345 schema: "radroots_replica_schema".to_string(), 13346 storage: "crates/replica_store".to_string(), 13347 sync: "radroots_replica_sync".to_string(), 13348 }); 13349 }, 13350 ); 13351 assert_bundle_error("version.contract.version is required", |bundle| { 13352 bundle.version.contract.version.clear(); 13353 }); 13354 assert_bundle_error("version.contract.stability is required", |bundle| { 13355 bundle.version.contract.stability.clear(); 13356 }); 13357 assert_bundle_error("version.semver rules must all be non-empty", |bundle| { 13358 bundle.version.semver.major_on.clear(); 13359 }); 13360 assert_bundle_error("version.semver rules must all be non-empty", |bundle| { 13361 bundle.version.semver.minor_on.clear(); 13362 }); 13363 assert_bundle_error("version.semver rules must all be non-empty", |bundle| { 13364 bundle.version.semver.patch_on.clear(); 13365 }); 13366 assert_bundle_error( 13367 "release_integrity.requires_conformance_pass must be true", 13368 |bundle| { 13369 bundle.version.release_integrity.requires_conformance_pass = false; 13370 }, 13371 ); 13372 assert_bundle_error( 13373 "release_integrity.requires_contract_manifest_diff must be true", 13374 |bundle| { 13375 bundle 13376 .version 13377 .release_integrity 13378 .requires_contract_manifest_diff = false; 13379 }, 13380 ); 13381 assert_bundle_error( 13382 "release_integrity.requires_release_notes must be true", 13383 |bundle| { 13384 bundle.version.release_integrity.requires_release_notes = false; 13385 }, 13386 ); 13387 assert_bundle_error("contract policy flags must all be true", |bundle| { 13388 bundle.manifest.policy.exclude_internal_workspace_crates = false; 13389 }); 13390 assert_bundle_error("contract policy flags must all be true", |bundle| { 13391 bundle.manifest.policy.require_reproducible_exports = false; 13392 }); 13393 assert_bundle_error("contract policy flags must all be true", |bundle| { 13394 bundle.manifest.policy.require_conformance_vectors = false; 13395 }); 13396 assert_bundle_error("contract replica policy flags must all be true", |bundle| { 13397 bundle.manifest.policy.replica = Some(ReplicaPolicy { 13398 forbid_legacy_alias_identifiers: false, 13399 require_transport_agnostic_sync_contract: true, 13400 require_deterministic_emit_ingest: true, 13401 }); 13402 }); 13403 13404 let _ = fs::remove_dir_all(root); 13405 } 13406 13407 #[test] 13408 fn load_contract_bundle_rejects_stale_consumer_sdk_tables() { 13409 let stale_manifest_root = create_synthetic_workspace("stale_manifest_consumer_sdk"); 13410 let manifest_path = stale_manifest_root.join("contracts").join("manifest.toml"); 13411 let mut manifest = fs::read_to_string(&manifest_path).expect("manifest"); 13412 manifest.push_str( 13413 r#" 13414 [consumer_sdk] 13415 rust_package = "radroots_sdk" 13416 "#, 13417 ); 13418 write_file(&manifest_path, &manifest); 13419 let manifest_err = 13420 load_contract_bundle(&stale_manifest_root).expect_err("stale manifest table"); 13421 assert!(manifest_err.contains("manifest.toml")); 13422 assert!(manifest_err.contains("consumer_sdk")); 13423 let _ = fs::remove_dir_all(stale_manifest_root); 13424 13425 let stale_operations_root = create_synthetic_workspace("stale_operations_consumer_sdk"); 13426 add_operation_contract_files(&stale_operations_root); 13427 let operations_path = stale_operations_root 13428 .join("contracts") 13429 .join("operations.toml"); 13430 let mut operations = fs::read_to_string(&operations_path).expect("operations"); 13431 operations.push_str( 13432 r#" 13433 [consumer_sdk] 13434 rust_package = "radroots_sdk" 13435 "#, 13436 ); 13437 write_file(&operations_path, &operations); 13438 let operations_err = 13439 load_contract_bundle(&stale_operations_root).expect_err("stale operations table"); 13440 assert!(operations_err.contains("operations.toml")); 13441 assert!(operations_err.contains("consumer_sdk")); 13442 let _ = fs::remove_dir_all(stale_operations_root); 13443 } 13444 13445 #[test] 13446 fn load_contract_bundle_rejects_legacy_contract_roots() { 13447 let stale_spec_root = create_synthetic_workspace("stale_spec_root"); 13448 fs::create_dir_all(stale_spec_root.join("spec")).expect("create spec root"); 13449 let spec_err = load_contract_bundle(&stale_spec_root).expect_err("stale spec root"); 13450 assert!(spec_err.contains("legacy contract root")); 13451 assert!(spec_err.contains("spec")); 13452 let _ = fs::remove_dir_all(stale_spec_root); 13453 13454 let stale_policy_root = create_synthetic_workspace("stale_policy_root"); 13455 fs::create_dir_all(stale_policy_root.join("policy")).expect("create policy root"); 13456 let policy_err = load_contract_bundle(&stale_policy_root).expect_err("stale policy root"); 13457 assert!(policy_err.contains("legacy contract root")); 13458 assert!(policy_err.contains("policy")); 13459 let _ = fs::remove_dir_all(stale_policy_root); 13460 } 13461 13462 #[test] 13463 fn load_contract_bundle_requires_operations_manifest() { 13464 let root = create_synthetic_workspace("missing_operations_manifest"); 13465 fs::remove_file(root.join("contracts").join("operations.toml")) 13466 .expect("remove operations manifest"); 13467 13468 let error = load_contract_bundle(&root).expect_err("missing operations manifest"); 13469 assert!(error.contains("operations.toml"), "{error}"); 13470 assert!(error.contains("read"), "{error}"); 13471 13472 let _ = fs::remove_dir_all(root); 13473 } 13474 13475 #[test] 13476 fn capsule_operation_authority_cannot_be_disabled_by_domain_removal() { 13477 let root = workspace_root(); 13478 13479 let mut social_bundle = load_contract_bundle(&root).expect("load current contract"); 13480 social_bundle 13481 .operations_manifest 13482 .public 13483 .domains 13484 .retain(|domain| domain != "social"); 13485 social_bundle 13486 .operations_manifest 13487 .operations 13488 .retain(|_, operation| operation.domain != "social"); 13489 let social_error = validate_contract_bundle(&social_bundle) 13490 .expect_err("removing social authority must fail"); 13491 assert!( 13492 social_error.contains("comment operation authority drift"), 13493 "{social_error}" 13494 ); 13495 13496 let mut food_bundle = load_contract_bundle(&root).expect("load current contract"); 13497 food_bundle 13498 .operations_manifest 13499 .public 13500 .domains 13501 .retain(|domain| domain != "food_availability"); 13502 food_bundle 13503 .operations_manifest 13504 .operations 13505 .retain(|_, operation| operation.domain != "food_availability"); 13506 let food_error = validate_contract_bundle(&food_bundle) 13507 .expect_err("removing FoodAvailability authority must fail"); 13508 assert!( 13509 food_error.contains("food availability operation authority drift"), 13510 "{food_error}" 13511 ); 13512 } 13513 13514 #[test] 13515 fn validate_contract_bundle_reports_operation_contract_errors() { 13516 let root = create_synthetic_workspace("operation_contract_bundle_errors"); 13517 add_operation_contract_files(&root); 13518 13519 let assert_bundle_error = |expected: &str, mutator: fn(&mut ContractBundle)| { 13520 let mut bundle = load_contract_bundle(&root).expect("load bundle"); 13521 mutator(&mut bundle); 13522 let err = 13523 validate_generic_contract_bundle(&bundle).expect_err("bundle validation error"); 13524 assert!(err.contains(expected), "expected `{expected}` in `{err}`"); 13525 }; 13526 13527 assert_bundle_error("public.domains must not be empty", |bundle| { 13528 bundle.operations_manifest.public.domains.clear(); 13529 }); 13530 assert_bundle_error( 13531 "shared_types.public uses retired event type RadrootsNostrEvent", 13532 |bundle| { 13533 bundle 13534 .operations_manifest 13535 .shared_types 13536 .public 13537 .push("RadrootsNostrEvent".to_string()); 13538 }, 13539 ); 13540 assert_bundle_error( 13541 "shared_types.public uses retired event type RadrootsInboundCalendarDateEvent", 13542 |bundle| { 13543 bundle 13544 .operations_manifest 13545 .shared_types 13546 .public 13547 .push("RadrootsInboundCalendarDateEvent".to_string()); 13548 }, 13549 ); 13550 assert_bundle_error( 13551 "shared_types.public uses retired event type RadrootsCalendar", 13552 |bundle| { 13553 bundle 13554 .operations_manifest 13555 .shared_types 13556 .public 13557 .push("RadrootsCalendar".to_string()); 13558 }, 13559 ); 13560 assert_bundle_error( 13561 "shared_types.public uses retired event type RadrootsCalendarEventRsvp", 13562 |bundle| { 13563 bundle 13564 .operations_manifest 13565 .shared_types 13566 .public 13567 .push("RadrootsCalendarEventRsvp".to_string()); 13568 }, 13569 ); 13570 assert_bundle_error( 13571 "shared_types.public uses retired event type RadrootsCalendarRsvp", 13572 |bundle| { 13573 bundle 13574 .operations_manifest 13575 .shared_types 13576 .public 13577 .push("RadrootsCalendarRsvp".to_string()); 13578 }, 13579 ); 13580 assert_bundle_error( 13581 "operation profile.build_authored_draft inputs uses retired event type RadrootsNostrEvent", 13582 |bundle| { 13583 bundle 13584 .operations_manifest 13585 .operations 13586 .get_mut("profile_build_authored_draft") 13587 .expect("profile operation") 13588 .inputs 13589 .push("RadrootsNostrEvent".to_string()); 13590 }, 13591 ); 13592 assert_bundle_error( 13593 "operation profile.build_authored_draft outputs uses retired event type WireEventParts", 13594 |bundle| { 13595 bundle 13596 .operations_manifest 13597 .operations 13598 .get_mut("profile_build_authored_draft") 13599 .expect("profile operation") 13600 .outputs 13601 .push("WireEventParts".to_string()); 13602 }, 13603 ); 13604 assert_bundle_error( 13605 "operation profile.build_authored_draft implementation.rust_types uses retired event type RadrootsNostrEventPtr", 13606 |bundle| { 13607 bundle 13608 .operations_manifest 13609 .operations 13610 .get_mut("profile_build_authored_draft") 13611 .expect("profile operation") 13612 .implementation 13613 .rust_types 13614 .push("radroots_event::RadrootsNostrEventPtr".to_string()); 13615 }, 13616 ); 13617 let _ = fs::remove_dir_all(root); 13618 } 13619 13620 #[test] 13621 fn validate_contract_bundle_requires_real_conformance_assets() { 13622 let missing_schema_root = create_synthetic_workspace("operation_contract_missing_schema"); 13623 add_operation_contract_files(&missing_schema_root); 13624 let _ = fs::remove_file(conformance_schema_path(&missing_schema_root)); 13625 let bundle = load_contract_bundle(&missing_schema_root).expect("load bundle"); 13626 let err = 13627 validate_generic_contract_bundle(&bundle).expect_err("missing schema should fail"); 13628 assert!(err.contains("vector.schema.json")); 13629 let _ = fs::remove_dir_all(&missing_schema_root); 13630 13631 let invalid_vector_root = create_synthetic_workspace("operation_contract_invalid_vector"); 13632 add_operation_contract_files(&invalid_vector_root); 13633 let invalid_vector_path = invalid_vector_root 13634 .join("contracts") 13635 .join("conformance") 13636 .join("vectors") 13637 .join("profile") 13638 .join("metadata.v1.json"); 13639 write_file( 13640 &invalid_vector_path, 13641 r#"{ 13642 "suite": "profile", 13643 "contract_version": "1.0.0", 13644 "vectors": [ 13645 { 13646 "id": "profile_build_authored_draft_minimal_001", 13647 "kind": "profile.build_authored_draft", 13648 "input": {} 13649 } 13650 ] 13651 } 13652 "#, 13653 ); 13654 let bundle = load_contract_bundle(&invalid_vector_root).expect("load bundle"); 13655 let err = 13656 validate_generic_contract_bundle(&bundle).expect_err("invalid vector should fail"); 13657 assert!(err.contains("metadata.v1.json")); 13658 assert!(err.contains("exactly one of expected or expected_error_contains")); 13659 13660 write_file( 13661 &invalid_vector_path, 13662 r#"{ 13663 "suite": "profile", 13664 "contract_version": "1.0.0", 13665 "vectors": [ 13666 { 13667 "id": "profile_build_authored_draft_minimal_001", 13668 "kind": "profile.build_authored_draft", 13669 "input": {}, 13670 "expected": {}, 13671 "expected_error_contains": "invalid" 13672 } 13673 ] 13674 } 13675 "#, 13676 ); 13677 let err = validate_generic_contract_bundle(&bundle) 13678 .expect_err("vector with two result authorities should fail"); 13679 assert!(err.contains("exactly one of expected or expected_error_contains")); 13680 13681 write_file( 13682 &invalid_vector_path, 13683 r#"{ 13684 "suite": "profile", 13685 "contract_version": "1.0.0", 13686 "vectors": [ 13687 { 13688 "id": "profile_build_authored_draft_minimal_001", 13689 "kind": "profile.build_authored_draft", 13690 "input": {}, 13691 "expected_error_contains": " " 13692 } 13693 ] 13694 } 13695 "#, 13696 ); 13697 let err = validate_generic_contract_bundle(&bundle) 13698 .expect_err("blank expected error fragment should fail"); 13699 assert!(err.contains("expected_error_contains must not be blank")); 13700 let _ = fs::remove_dir_all(&invalid_vector_root); 13701 13702 let root = create_synthetic_workspace("operation_contract_vector_path"); 13703 add_operation_contract_files(&root); 13704 let mut bundle = load_contract_bundle(&root).expect("load bundle"); 13705 bundle 13706 .operations_manifest 13707 .operations 13708 .get_mut("profile_build_authored_draft") 13709 .expect("profile operation") 13710 .conformance 13711 .vector = "conformance/vectors/profile/metadata.v1.json".to_string(); 13712 let err = validate_generic_contract_bundle(&bundle).expect_err("legacy path should fail"); 13713 assert!(err.contains("must live under contracts/conformance/")); 13714 let _ = fs::remove_dir_all(root); 13715 } 13716 13717 #[test] 13718 fn parse_toml_and_publish_flags_report_failures() { 13719 let missing = temp_root("parse_toml_missing"); 13720 let read_err = 13721 parse_toml::<WorkspaceCargoManifest>(&missing.join("Cargo.toml")).expect_err("missing"); 13722 assert!(read_err.contains("read")); 13723 let _ = fs::remove_dir_all(&missing); 13724 13725 let invalid = temp_root("parse_toml_invalid"); 13726 write_file(&invalid.join("Cargo.toml"), "[workspace]\nmembers = ["); 13727 let parse_err = parse_toml::<WorkspaceCargoManifest>(&invalid.join("Cargo.toml")) 13728 .expect_err("invalid manifest"); 13729 assert!(parse_err.contains("parse")); 13730 let _ = fs::remove_dir_all(&invalid); 13731 13732 let contract_manifest_missing = temp_root("parse_contract_manifest_missing"); 13733 let contract_manifest_read_err = 13734 parse_toml::<ContractManifest>(&contract_manifest_missing.join("manifest.toml")) 13735 .expect_err("missing contract manifest"); 13736 assert!(contract_manifest_read_err.contains("read")); 13737 let _ = fs::remove_dir_all(&contract_manifest_missing); 13738 13739 let contract_manifest_invalid = temp_root("parse_contract_manifest_invalid"); 13740 write_file( 13741 &contract_manifest_invalid.join("manifest.toml"), 13742 "[contract", 13743 ); 13744 let contract_manifest_parse_err = 13745 parse_toml::<ContractManifest>(&contract_manifest_invalid.join("manifest.toml")) 13746 .expect_err("invalid contract manifest"); 13747 assert!(contract_manifest_parse_err.contains("parse")); 13748 let _ = fs::remove_dir_all(&contract_manifest_invalid); 13749 13750 let version_missing = temp_root("parse_version_policy_missing"); 13751 let version_read_err = parse_toml::<VersionPolicy>(&version_missing.join("version.toml")) 13752 .expect_err("missing version policy"); 13753 assert!(version_read_err.contains("read")); 13754 let _ = fs::remove_dir_all(&version_missing); 13755 13756 let version_invalid = temp_root("parse_version_policy_invalid"); 13757 write_file(&version_invalid.join("version.toml"), "[version"); 13758 let version_parse_err = parse_toml::<VersionPolicy>(&version_invalid.join("version.toml")) 13759 .expect_err("invalid version policy"); 13760 assert!(version_parse_err.contains("parse")); 13761 let _ = fs::remove_dir_all(&version_invalid); 13762 13763 let release_missing = temp_root("parse_release_contract_missing"); 13764 let release_read_err = 13765 parse_toml::<ReleaseContractFile>(&release_missing.join("publish-set.toml")) 13766 .expect_err("missing release contract"); 13767 assert!(release_read_err.contains("read")); 13768 let _ = fs::remove_dir_all(&release_missing); 13769 13770 let release_invalid = temp_root("parse_release_contract_invalid"); 13771 write_file(&release_invalid.join("publish-set.toml"), "[release"); 13772 let release_parse_err = 13773 parse_toml::<ReleaseContractFile>(&release_invalid.join("publish-set.toml")) 13774 .expect_err("invalid release contract"); 13775 assert!(release_parse_err.contains("parse")); 13776 let _ = fs::remove_dir_all(&release_invalid); 13777 13778 let operations_missing = temp_root("parse_operations_manifest_missing"); 13779 let operations_read_err = 13780 parse_toml::<OperationsContractManifest>(&operations_missing.join("operations.toml")) 13781 .expect_err("missing operations manifest"); 13782 assert!(operations_read_err.contains("read")); 13783 let _ = fs::remove_dir_all(&operations_missing); 13784 13785 let operations_invalid = temp_root("parse_operations_manifest_invalid"); 13786 write_file(&operations_invalid.join("operations.toml"), "[operations"); 13787 let operations_parse_err = 13788 parse_toml::<OperationsContractManifest>(&operations_invalid.join("operations.toml")) 13789 .expect_err("invalid operations manifest"); 13790 assert!(operations_parse_err.contains("parse")); 13791 let _ = fs::remove_dir_all(&operations_invalid); 13792 13793 let dup = temp_root("publish_flags_duplicate"); 13794 write_file( 13795 &dup.join("Cargo.toml"), 13796 r#"[workspace] 13797 members = ["crates/a", "crates/b"] 13798 "#, 13799 ); 13800 let member_manifest = 13801 "[package]\nname = \"duplicate\"\nversion = \"0.1.0\"\nedition = \"2024\"\n"; 13802 write_file( 13803 &dup.join("crates").join("a").join("Cargo.toml"), 13804 member_manifest, 13805 ); 13806 write_file( 13807 &dup.join("crates").join("b").join("Cargo.toml"), 13808 member_manifest, 13809 ); 13810 let dup_err = workspace_package_publish_flags(&dup).expect_err("duplicate publish flags"); 13811 assert!(dup_err.contains("duplicate workspace package name")); 13812 let _ = fs::remove_dir_all(&dup); 13813 } 13814 13815 #[test] 13816 fn workspace_package_records_and_callers_report_member_manifest_errors() { 13817 let root = temp_root("workspace_package_record_errors"); 13818 write_file( 13819 &root.join("Cargo.toml"), 13820 r#"[workspace] 13821 members = ["crates/a"] 13822 "#, 13823 ); 13824 13825 let read_err = 13826 workspace_package_records(&root).expect_err("missing member manifest should fail"); 13827 assert!(read_err.contains("read")); 13828 13829 let names_err = workspace_package_names(&root).expect_err("names should fail"); 13830 assert!(names_err.contains("read")); 13831 let manifests_err = workspace_package_manifests(&root).expect_err("manifests should fail"); 13832 assert!(manifests_err.contains("read")); 13833 let flags_err = workspace_package_publish_flags(&root).expect_err("flags should fail"); 13834 assert!(flags_err.contains("read")); 13835 let deps_err = read_workspace_package_dependencies(&root).expect_err("deps should fail"); 13836 assert!(deps_err.contains("read")); 13837 13838 let publish = ["radroots_a".to_string()] 13839 .into_iter() 13840 .collect::<BTreeSet<_>>(); 13841 let publish_err = 13842 validate_publish_package_metadata(&root, &publish).expect_err("publish metadata"); 13843 assert!(publish_err.contains("read")); 13844 13845 write_file( 13846 &root.join("crates").join("a").join("Cargo.toml"), 13847 "[package", 13848 ); 13849 let parse_value_err = 13850 workspace_package_records(&root).expect_err("invalid toml should fail"); 13851 assert!(parse_value_err.contains("parse")); 13852 13853 write_file( 13854 &root.join("crates").join("a").join("Cargo.toml"), 13855 r#"[workspace] 13856 resolver = "2" 13857 "#, 13858 ); 13859 let parse_package_err = 13860 workspace_package_records(&root).expect_err("missing package table should fail"); 13861 assert!(parse_package_err.contains("parse")); 13862 13863 let _ = fs::remove_dir_all(&root); 13864 } 13865 13866 #[test] 13867 fn workspace_package_manifests_success_and_publish_metadata_duplicate_names() { 13868 let root = create_synthetic_workspace("workspace_manifest_success"); 13869 let manifests = workspace_package_manifests(&root).expect("workspace manifests"); 13870 assert_eq!(manifests.len(), 2); 13871 assert!(manifests.contains_key("radroots_a")); 13872 assert!(manifests.contains_key("radroots_b")); 13873 13874 write_file( 13875 &root.join("crates").join("b").join("Cargo.toml"), 13876 r#"[package] 13877 name = "radroots_a" 13878 version = "1.0.0" 13879 edition = "2024" 13880 description = "crate b duplicate name" 13881 repository = "https://example.com/b" 13882 homepage = "https://example.com/b" 13883 documentation = "https://docs.example.com/b" 13884 readme = "README" 13885 publish = false 13886 "#, 13887 ); 13888 let publish = ["radroots_a".to_string()] 13889 .into_iter() 13890 .collect::<BTreeSet<_>>(); 13891 let duplicate_err = 13892 validate_publish_package_metadata(&root, &publish).expect_err("duplicate package map"); 13893 assert!(duplicate_err.contains("duplicate workspace package name")); 13894 13895 let _ = fs::remove_dir_all(&root); 13896 } 13897 13898 #[test] 13899 fn workspace_package_publish_configs_cover_success_and_duplicate_names() { 13900 let root = create_synthetic_workspace("workspace_publish_configs"); 13901 let flags = workspace_package_publish_flags(&root).expect("publish flags"); 13902 assert!(flags["radroots_a"]); 13903 assert!(!flags["radroots_b"]); 13904 13905 let configs = workspace_package_publish_configs(&root).expect("publish configs"); 13906 assert_eq!( 13907 configs["radroots_a"], 13908 Some(PackagePublish::Registries(vec!["crates-io".to_string()])) 13909 ); 13910 assert_eq!(configs["radroots_b"], Some(PackagePublish::Bool(false))); 13911 13912 write_file( 13913 &root.join("crates").join("b").join("Cargo.toml"), 13914 r#"[package] 13915 name = "radroots_a" 13916 version = "1.0.0" 13917 edition = "2024" 13918 publish = false 13919 "#, 13920 ); 13921 let duplicate_err = workspace_package_publish_configs(&root) 13922 .expect_err("duplicate package name in publish configs"); 13923 assert!(duplicate_err.contains("duplicate workspace package name")); 13924 13925 let _ = fs::remove_dir_all(&root); 13926 } 13927 13928 #[test] 13929 fn workspace_package_publish_configs_report_workspace_record_errors() { 13930 let root = temp_root("workspace_publish_configs_errors"); 13931 let err = workspace_package_publish_configs(&root) 13932 .expect_err("missing workspace manifest should fail"); 13933 assert!(err.contains("Cargo.toml")); 13934 13935 let _ = fs::remove_dir_all(&root); 13936 } 13937 13938 #[test] 13939 fn coverage_release_and_bundle_loaders_report_parse_and_read_errors() { 13940 let root = create_synthetic_workspace("coverage_release_loader_errors"); 13941 let contract_root = root.join("contracts"); 13942 let coverage_root = coverage_root(&contract_root); 13943 let release_policy_path = root_release_policy_path(&root); 13944 13945 let missing_workspace = temp_root("coverage_missing_workspace_manifest"); 13946 let policy_workspace_err = 13947 validate_coverage_policy_parity(&missing_workspace, &contract_root) 13948 .expect_err("coverage workspace lookup error"); 13949 assert!(policy_workspace_err.contains("Cargo.toml")); 13950 let _ = fs::remove_dir_all(&missing_workspace); 13951 13952 let _ = fs::remove_file(coverage_root.join("coverage.toml")); 13953 let policy_load_err = validate_coverage_policy_parity(&root, &contract_root) 13954 .expect_err("coverage policy read error"); 13955 assert!(policy_load_err.contains("coverage.toml")); 13956 write_file( 13957 &coverage_root.join("coverage.toml"), 13958 r#"[gate] 13959 fail_under_exec_lines = 100.0 13960 fail_under_functions = 100.0 13961 fail_under_regions = 100.0 13962 fail_under_branches = 100.0 13963 require_branches = true 13964 13965 [required] 13966 crates = ["radroots_a", "radroots_b"] 13967 "#, 13968 ); 13969 13970 let missing_release = temp_root("release_missing_workspace_manifest"); 13971 write_root_release_policy( 13972 &missing_release, 13973 r#"[release] 13974 version = "1.0.0" 13975 13976 [publish] 13977 crates = ["radroots_a"] 13978 13979 [internal] 13980 crates = ["radroots_b"] 13981 13982 [publish_order] 13983 crates = ["radroots_a"] 13984 "#, 13985 ); 13986 let release_workspace_err = 13987 validate_release_publish_policy(&missing_release, &contract_root, "1.0.0") 13988 .expect_err("release workspace read error"); 13989 assert!(release_workspace_err.contains("Cargo.toml")); 13990 let _ = fs::remove_dir_all(&missing_release); 13991 13992 let _ = fs::remove_file(&release_policy_path); 13993 let release_load_err = validate_release_publish_policy(&root, &contract_root, "1.0.0") 13994 .expect_err("release contract read error"); 13995 assert!(release_load_err.contains(RELEASE_POLICY_RELATIVE)); 13996 13997 write_file( 13998 &release_policy_path, 13999 r#"[release] 14000 version = "1.0.0" 14001 14002 [publish] 14003 crates = ["radroots_a", "radroots_a"] 14004 14005 [internal] 14006 crates = ["radroots_b"] 14007 14008 [publish_order] 14009 crates = ["radroots_a"] 14010 "#, 14011 ); 14012 let duplicate_publish = validate_release_publish_policy(&root, &contract_root, "1.0.0") 14013 .expect_err("duplicate publish crates"); 14014 assert!(duplicate_publish.contains("publish.crates has duplicate crate")); 14015 14016 write_file( 14017 &release_policy_path, 14018 r#"[release] 14019 version = "1.0.0" 14020 14021 [publish] 14022 crates = ["radroots_a"] 14023 14024 [internal] 14025 crates = ["radroots_b", "radroots_b"] 14026 14027 [publish_order] 14028 crates = ["radroots_a"] 14029 "#, 14030 ); 14031 let duplicate_internal = validate_release_publish_policy(&root, &contract_root, "1.0.0") 14032 .expect_err("duplicate internal crates"); 14033 assert!(duplicate_internal.contains("internal.crates has duplicate crate")); 14034 14035 write_file( 14036 &release_policy_path, 14037 r#"[release] 14038 version = "1.0.0" 14039 14040 [publish] 14041 crates = ["radroots_a"] 14042 14043 [internal] 14044 crates = ["radroots_b"] 14045 14046 [publish_order] 14047 crates = ["radroots_a", "radroots_a"] 14048 "#, 14049 ); 14050 let duplicate_order = validate_release_publish_policy(&root, &contract_root, "1.0.0") 14051 .expect_err("duplicate publish order"); 14052 assert!(duplicate_order.contains("publish_order.crates has duplicate crate")); 14053 14054 write_file( 14055 &release_policy_path, 14056 r#"[release] 14057 version = "1.0.0" 14058 14059 [publish] 14060 crates = ["radroots_a"] 14061 14062 [internal] 14063 crates = ["radroots_b"] 14064 14065 [publish_order] 14066 crates = ["radroots_a"] 14067 "#, 14068 ); 14069 write_file( 14070 &root.join("crates").join("a").join("Cargo.toml"), 14071 "[package", 14072 ); 14073 let dependency_err = validate_release_publish_policy(&root, &contract_root, "1.0.0") 14074 .expect_err("workspace dependency parse error"); 14075 assert!(dependency_err.contains("parse")); 14076 14077 let _ = fs::remove_dir_all(&root); 14078 } 14079 14080 #[test] 14081 fn load_release_contract_with_override_reports_override_and_missing_policy_errors() { 14082 let root = create_synthetic_workspace("release_contract_loader_errors"); 14083 14084 let missing_override = root.join("missing-release-policy.toml"); 14085 let override_err = 14086 load_release_contract_with_override(&root, "1.0.0", Some(missing_override.clone())) 14087 .expect_err("missing override should fail"); 14088 assert!(override_err.contains("release policy override points to a missing file")); 14089 14090 let _ = fs::remove_file(root_release_policy_path(&root)); 14091 let missing_policy_err = load_release_contract_with_override(&root, "1.0.0", None) 14092 .expect_err("missing release policy should fail"); 14093 assert!(missing_policy_err.contains("release publish policy not found")); 14094 assert!(missing_policy_err.contains(RELEASE_POLICY_RELATIVE)); 14095 14096 let _ = fs::remove_dir_all(&root); 14097 } 14098 14099 #[test] 14100 fn release_contract_discovery_does_not_search_parent_directories() { 14101 let parent = temp_root("release_contract_parent_isolation"); 14102 write_file( 14103 &release_contract_path(&parent, "1.0.0"), 14104 "[release]\nversion = \"1.0.0\"\n\n[publish_order]\ncrates = []\n", 14105 ); 14106 let capsule = parent.join("capsule"); 14107 fs::create_dir_all(&capsule).expect("create isolated capsule root"); 14108 14109 let err = resolve_release_contract_path_with_override(&capsule, "1.0.0", None) 14110 .expect_err("parent release contract must be ignored"); 14111 assert!(err.contains(&capsule.display().to_string())); 14112 assert!( 14113 !err.contains( 14114 &release_contract_path(&parent, "1.0.0") 14115 .display() 14116 .to_string() 14117 ) 14118 ); 14119 14120 let _ = fs::remove_dir_all(&parent); 14121 } 14122 14123 #[test] 14124 fn root_release_policy_preflight_covers_classification_variants() { 14125 let root = create_synthetic_workspace("root_release_policy_classifications"); 14126 configure_root_release_policy_workspace(&root); 14127 write_root_release_policy( 14128 &root, 14129 r#"[release] 14130 version = "1.0.0" 14131 14132 [classification] 14133 public = ["radroots_a"] 14134 internal = ["radroots_b"] 14135 deferred = ["radroots_c"] 14136 retired = ["radroots_d"] 14137 yank_only = ["radroots_e"] 14138 14139 [publish_order] 14140 crates = ["radroots_a"] 14141 "#, 14142 ); 14143 14144 let bundle = load_contract_bundle(&root).expect("load root release policy bundle"); 14145 validate_generic_contract_bundle(&bundle).expect("validate root release policy bundle"); 14146 validate_generic_release_preflight(&root).expect("validate root release policy preflight"); 14147 14148 let _ = fs::remove_dir_all(&root); 14149 } 14150 14151 #[test] 14152 fn root_release_policy_reports_deferred_retired_and_yank_only_errors() { 14153 for (label, policy_body, expected) in [ 14154 ( 14155 "deferred", 14156 r#"[release] 14157 version = "1.0.0" 14158 14159 [classification] 14160 public = ["radroots_a"] 14161 internal = ["radroots_b"] 14162 deferred = ["radroots_c", "radroots_c"] 14163 retired = ["radroots_d"] 14164 yank_only = ["radroots_e"] 14165 14166 [publish_order] 14167 crates = ["radroots_a"] 14168 "#, 14169 "classification.deferred has duplicate crate radroots_c", 14170 ), 14171 ( 14172 "retired", 14173 r#"[release] 14174 version = "1.0.0" 14175 14176 [classification] 14177 public = ["radroots_a"] 14178 internal = ["radroots_b"] 14179 deferred = ["radroots_c"] 14180 retired = [""] 14181 yank_only = ["radroots_e"] 14182 14183 [publish_order] 14184 crates = ["radroots_a"] 14185 "#, 14186 "classification.retired contains an empty crate name", 14187 ), 14188 ( 14189 "yank_only", 14190 r#"[release] 14191 version = "1.0.0" 14192 14193 [classification] 14194 public = ["radroots_a"] 14195 internal = ["radroots_b"] 14196 deferred = ["radroots_c"] 14197 retired = ["radroots_d"] 14198 yank_only = ["radroots_e", "radroots_e"] 14199 14200 [publish_order] 14201 crates = ["radroots_a"] 14202 "#, 14203 "classification.yank_only has duplicate crate radroots_e", 14204 ), 14205 ] { 14206 let root = create_synthetic_workspace(&format!("root_release_policy_{label}_error")); 14207 configure_root_release_policy_workspace(&root); 14208 write_root_release_policy(&root, policy_body); 14209 14210 let err = validate_release_publish_policy(&root, &root.join("contracts"), "1.0.0") 14211 .expect_err("invalid non-public classification should fail"); 14212 assert!(err.contains(expected), "{label} err: {err}"); 14213 14214 let _ = fs::remove_dir_all(&root); 14215 } 14216 } 14217 14218 #[test] 14219 fn validate_release_preflight_reports_each_stage_error() { 14220 let missing_contract_root = temp_root("preflight_missing_contract"); 14221 let missing_contract_err = validate_generic_release_preflight(&missing_contract_root) 14222 .expect_err("missing contract"); 14223 assert!(missing_contract_err.contains("manifest.toml")); 14224 let _ = fs::remove_dir_all(&missing_contract_root); 14225 14226 let invalid_bundle = create_synthetic_workspace("preflight_invalid_bundle"); 14227 write_file( 14228 &invalid_bundle.join("contracts").join("manifest.toml"), 14229 r#"[contract] 14230 name = "radroots_contract" 14231 version = "1.0.0" 14232 source = "synthetic" 14233 14234 [surface] 14235 model_crates = ["radroots_a"] 14236 algorithm_crates = ["radroots_b"] 14237 14238 [policy] 14239 exclude_internal_workspace_crates = false 14240 require_reproducible_exports = true 14241 require_conformance_vectors = true 14242 "#, 14243 ); 14244 let invalid_bundle_err = 14245 validate_generic_release_preflight(&invalid_bundle).expect_err("bundle validation"); 14246 assert!(invalid_bundle_err.contains("contract policy flags must all be true")); 14247 let _ = fs::remove_dir_all(&invalid_bundle); 14248 14249 let missing_release = create_synthetic_workspace("preflight_missing_release"); 14250 let _ = fs::remove_file(root_release_policy_path(&missing_release)); 14251 let missing_release_err = 14252 validate_generic_release_preflight(&missing_release).expect_err("missing release"); 14253 assert!(missing_release_err.contains(RELEASE_POLICY_RELATIVE)); 14254 let _ = fs::remove_dir_all(&missing_release); 14255 14256 let missing_required = create_synthetic_workspace("preflight_missing_required"); 14257 let _ = fs::remove_file(missing_required.join("contracts").join("coverage.toml")); 14258 let missing_required_err = validate_generic_release_preflight(&missing_required) 14259 .expect_err("missing required list"); 14260 assert!(missing_required_err.contains("coverage.toml")); 14261 let _ = fs::remove_dir_all(&missing_required); 14262 14263 let duplicate_publish = create_synthetic_workspace("preflight_duplicate_publish"); 14264 write_file( 14265 &root_release_policy_path(&duplicate_publish), 14266 r#"[release] 14267 version = "1.0.0" 14268 14269 [publish] 14270 crates = ["radroots_a", "radroots_a"] 14271 14272 [internal] 14273 crates = ["radroots_b"] 14274 14275 [publish_order] 14276 crates = ["radroots_a"] 14277 "#, 14278 ); 14279 let duplicate_publish_err = validate_generic_release_preflight(&duplicate_publish) 14280 .expect_err("duplicate publish crates"); 14281 assert!(duplicate_publish_err.contains("publish.crates has duplicate crate")); 14282 let _ = fs::remove_dir_all(&duplicate_publish); 14283 14284 let duplicate_required = create_synthetic_workspace("preflight_duplicate_required"); 14285 write_file( 14286 &duplicate_required.join("contracts").join("coverage.toml"), 14287 "[gate]\nfail_under_exec_lines = 100.0\nfail_under_functions = 100.0\nfail_under_regions = 100.0\nfail_under_branches = 100.0\nrequire_branches = true\n\n[required]\ncrates = [\"radroots_a\", \"radroots_a\"]\n", 14288 ); 14289 let duplicate_required_err = validate_generic_release_preflight(&duplicate_required) 14290 .expect_err("duplicate required crates"); 14291 assert!(duplicate_required_err.contains("duplicate crate")); 14292 let _ = fs::remove_dir_all(&duplicate_required); 14293 14294 let publish_metadata = create_synthetic_workspace("preflight_publish_metadata"); 14295 write_file( 14296 &publish_metadata.join("crates").join("a").join("Cargo.toml"), 14297 r#"[package] 14298 name = "radroots_a" 14299 publish = ["crates-io"] 14300 version = "1.0.0" 14301 edition = "2024" 14302 "#, 14303 ); 14304 let publish_metadata_err = validate_generic_release_preflight(&publish_metadata) 14305 .expect_err("publish metadata validation"); 14306 assert!(publish_metadata_err.contains("must define a non-empty package.description")); 14307 let _ = fs::remove_dir_all(&publish_metadata); 14308 14309 let missing_coverage_row = create_synthetic_workspace("preflight_missing_coverage_row"); 14310 write_file( 14311 &missing_coverage_row 14312 .join("target") 14313 .join("coverage") 14314 .join("coverage-refresh.tsv"), 14315 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\n", 14316 ); 14317 let missing_coverage_row_err = validate_generic_release_preflight(&missing_coverage_row) 14318 .expect_err("required coverage refresh row missing"); 14319 assert!(missing_coverage_row_err.contains("missing from coverage-refresh.tsv")); 14320 let _ = fs::remove_dir_all(&missing_coverage_row); 14321 } 14322 14323 #[test] 14324 fn load_contract_bundle_and_validation_report_version_core_and_coverage_errors() { 14325 let root = create_synthetic_workspace("bundle_version_core_and_coverage_errors"); 14326 write_file(&root.join("contracts").join("version.toml"), "[contract"); 14327 let version_parse_err = load_contract_bundle(&root).expect_err("invalid version file"); 14328 assert!(version_parse_err.contains("version.toml")); 14329 14330 write_file( 14331 &root.join("contracts").join("version.toml"), 14332 r#"[contract] 14333 version = "1.0.0" 14334 stability = "alpha" 14335 14336 [semver] 14337 major_on = ["breaking"] 14338 minor_on = ["feature"] 14339 patch_on = ["fix"] 14340 14341 [release_integrity] 14342 requires_conformance_pass = true 14343 requires_contract_manifest_diff = true 14344 requires_release_notes = true 14345 "#, 14346 ); 14347 let bundle = load_contract_bundle(&root).expect("load bundle"); 14348 write_file( 14349 &root.join("crates").join("core").join("src").join("unit.rs"), 14350 r#"pub enum UnitDimension { 14351 Mass, 14352 Count, 14353 Volume, 14354 } 14355 "#, 14356 ); 14357 let core_err = validate_generic_contract_bundle(&bundle).expect_err("core unit mismatch"); 14358 assert!(core_err.contains("variant order must be")); 14359 14360 write_file( 14361 &root.join("crates").join("core").join("src").join("unit.rs"), 14362 r#"pub enum UnitDimension { 14363 Count, 14364 Mass, 14365 Volume, 14366 } 14367 "#, 14368 ); 14369 write_file( 14370 &root.join("contracts").join("coverage.toml"), 14371 r#"[gate] 14372 fail_under_exec_lines = 90.0 14373 fail_under_functions = 90.0 14374 fail_under_regions = 90.0 14375 fail_under_branches = 90.0 14376 require_branches = false 14377 14378 [required] 14379 crates = ["radroots_a", "radroots_b"] 14380 "#, 14381 ); 14382 let policy_err = 14383 validate_generic_contract_bundle(&bundle).expect_err("coverage policy validation"); 14384 assert!(policy_err.contains("90/90/90/90")); 14385 14386 let _ = fs::remove_dir_all(&root); 14387 } 14388 14389 #[test] 14390 fn coverage_summary_and_core_enum_additional_error_paths() { 14391 let coverage_root = temp_root("coverage_summary_additional_errors"); 14392 write_file( 14393 &coverage_root 14394 .join("target") 14395 .join("coverage") 14396 .join("coverage-refresh.tsv"), 14397 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100\tbad\t100\t100\tfile\n", 14398 ); 14399 let func_err = load_coverage_refresh_rows(&coverage_root).expect_err("func parse error"); 14400 assert!(func_err.contains("parse func")); 14401 write_file( 14402 &coverage_root 14403 .join("target") 14404 .join("coverage") 14405 .join("coverage-refresh.tsv"), 14406 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\nradroots_a\tpass\t100\t100\tbad\t100\tfile\n", 14407 ); 14408 let branch_err = 14409 load_coverage_refresh_rows(&coverage_root).expect_err("branch parse error"); 14410 assert!(branch_err.contains("parse branch")); 14411 let _ = fs::remove_dir_all(&coverage_root); 14412 14413 let missing_refresh_root = temp_root("coverage_summary_missing_refresh"); 14414 let required = ["radroots_a".to_string()] 14415 .into_iter() 14416 .collect::<BTreeSet<_>>(); 14417 let missing_refresh_err = validate_required_coverage_summary( 14418 &missing_refresh_root, 14419 &required, 14420 required_thresholds(), 14421 ) 14422 .expect_err("missing refresh should fail"); 14423 assert!(missing_refresh_err.contains("coverage-refresh.tsv")); 14424 let _ = fs::remove_dir_all(&missing_refresh_root); 14425 14426 let enum_root = temp_root("core_unit_missing_enum"); 14427 write_file( 14428 &enum_root 14429 .join("crates") 14430 .join("core") 14431 .join("src") 14432 .join("unit.rs"), 14433 "pub struct NotTheEnum;", 14434 ); 14435 let enum_err = 14436 validate_core_unit_dimension_variant_order(&enum_root).expect_err("missing enum"); 14437 assert!(enum_err.contains("missing enum")); 14438 let _ = fs::remove_dir_all(&enum_root); 14439 } 14440 14441 #[test] 14442 fn publish_metadata_and_coverage_refresh_report_missing_paths() { 14443 let root = temp_root("publish_missing_manifest"); 14444 write_file( 14445 &root.join("Cargo.toml"), 14446 r#"[workspace] 14447 members = ["crates/a"] 14448 "#, 14449 ); 14450 write_file( 14451 &root.join("crates").join("a").join("Cargo.toml"), 14452 r#"[package] 14453 name = "radroots_a" 14454 version = "1.0.0" 14455 edition = "2024" 14456 authors = ["Radroots Test"] 14457 rust-version = "1.97" 14458 license = "MIT OR Apache-2.0" 14459 description = "crate a" 14460 repository = { workspace = true } 14461 homepage = { workspace = true } 14462 readme = { workspace = true } 14463 "#, 14464 ); 14465 let missing_manifest = ["radroots_b".to_string()] 14466 .into_iter() 14467 .collect::<BTreeSet<_>>(); 14468 let missing_err = validate_publish_package_metadata(&root, &missing_manifest) 14469 .expect_err("missing workspace manifest"); 14470 assert!(missing_err.contains("has no workspace manifest")); 14471 14472 let missing_field = ["radroots_a".to_string()] 14473 .into_iter() 14474 .collect::<BTreeSet<_>>(); 14475 let field_err = validate_publish_package_metadata(&root, &missing_field) 14476 .expect_err("missing configured field"); 14477 assert!(field_err.contains("must configure package.documentation")); 14478 14479 let refresh_missing = 14480 load_coverage_refresh_rows(&root).expect_err("missing coverage-refresh.tsv"); 14481 assert!(refresh_missing.contains("coverage-refresh.tsv")); 14482 let _ = fs::remove_dir_all(&root); 14483 } 14484 14485 #[test] 14486 fn coverage_refresh_parser_skips_blank_lines() { 14487 let root = temp_root("coverage_refresh_blank_lines"); 14488 write_file( 14489 &root 14490 .join("target") 14491 .join("coverage") 14492 .join("coverage-refresh.tsv"), 14493 "crate\tstatus\texec\tfunc\tbranch\tregion\treport\n\nradroots_a\tpass\t100\t100\t100\t100\tfile\n", 14494 ); 14495 let rows = load_coverage_refresh_rows(&root).expect("rows"); 14496 assert_eq!(rows.len(), 1); 14497 assert!(rows.contains_key("radroots_a")); 14498 let _ = fs::remove_dir_all(&root); 14499 } 14500 14501 #[test] 14502 fn core_unit_dimension_validation_reports_missing_and_mismatch() { 14503 let missing = temp_root("core_unit_missing"); 14504 let missing_err = validate_core_unit_dimension_variant_order(&missing) 14505 .expect_err("missing unit file should fail"); 14506 assert!(missing_err.contains("unit.rs")); 14507 let _ = fs::remove_dir_all(&missing); 14508 14509 let mismatch = temp_root("core_unit_mismatch"); 14510 write_file( 14511 &mismatch 14512 .join("crates") 14513 .join("core") 14514 .join("src") 14515 .join("unit.rs"), 14516 r#"pub enum UnitDimension { 14517 Mass, 14518 Count, 14519 Volume, 14520 } 14521 "#, 14522 ); 14523 let mismatch_err = validate_core_unit_dimension_variant_order(&mismatch) 14524 .expect_err("mismatched enum order should fail"); 14525 assert!(mismatch_err.contains("variant order must be")); 14526 let _ = fs::remove_dir_all(&mismatch); 14527 } 14528 14529 #[test] 14530 fn coverage_and_release_additional_error_branches_are_reported() { 14531 let root = create_synthetic_workspace("coverage_release_extra_errors"); 14532 let contract_root = root.join("contracts"); 14533 let coverage_root = coverage_root(&contract_root); 14534 let release_policy_path = root_release_policy_path(&root); 14535 14536 write_file( 14537 &coverage_root.join("coverage.toml"), 14538 r#"[gate] 14539 fail_under_exec_lines = 90.0 14540 fail_under_functions = 90.0 14541 fail_under_regions = 90.0 14542 fail_under_branches = 90.0 14543 require_branches = true 14544 14545 [required] 14546 crates = ["radroots_a", "radroots_b", "radroots_extra"] 14547 "#, 14548 ); 14549 let coverage_extra = validate_coverage_policy_parity(&root, &contract_root) 14550 .expect_err("coverage unknown crate"); 14551 assert!(coverage_extra.contains("includes excluded or unknown crates")); 14552 14553 write_file( 14554 &coverage_root.join("coverage.toml"), 14555 r#"[gate] 14556 fail_under_exec_lines = 90.0 14557 fail_under_functions = 90.0 14558 fail_under_regions = 90.0 14559 fail_under_branches = 90.0 14560 require_branches = true 14561 14562 [required] 14563 crates = ["radroots_b"] 14564 "#, 14565 ); 14566 let required_list_mismatch = validate_coverage_policy_parity(&root, &contract_root) 14567 .expect_err("required list must match workspace crates"); 14568 assert!(required_list_mismatch.contains("missing workspace crates")); 14569 14570 write_file( 14571 &release_policy_path, 14572 r#"[release] 14573 version = "1.0.0" 14574 14575 [publish] 14576 crates = ["radroots_a", "radroots_b", "radroots_extra"] 14577 14578 [internal] 14579 crates = [] 14580 14581 [publish_order] 14582 crates = ["radroots_a", "radroots_b"] 14583 "#, 14584 ); 14585 let release_extra = validate_release_publish_policy(&root, &contract_root, "1.0.0") 14586 .expect_err("release extra crate"); 14587 assert!(release_extra.contains("include unknown crates")); 14588 14589 write_file( 14590 &release_policy_path, 14591 r#"[release] 14592 version = "1.0.0" 14593 14594 [publish] 14595 crates = ["radroots_a"] 14596 14597 [internal] 14598 crates = ["radroots_b"] 14599 14600 [publish_order] 14601 crates = ["radroots_a", "radroots_b"] 14602 "#, 14603 ); 14604 let publish_order_extra = validate_release_publish_policy(&root, &contract_root, "1.0.0") 14605 .expect_err("publish order non-publish crate"); 14606 assert!(publish_order_extra.contains("non-publish crates")); 14607 14608 let _ = fs::remove_dir_all(&root); 14609 } 14610 14611 #[test] 14612 fn validate_contract_bundle_reports_release_policy_errors() { 14613 let release_error_root = create_synthetic_workspace("bundle_release_policy_error"); 14614 write_file( 14615 &root_release_policy_path(&release_error_root), 14616 r#"[release] 14617 version = "1.0.0" 14618 14619 [publish] 14620 crates = ["radroots_a"] 14621 14622 [internal] 14623 crates = ["radroots_b"] 14624 14625 [publish_order] 14626 crates = [] 14627 "#, 14628 ); 14629 let bundle = load_contract_bundle(&release_error_root).expect("load release error bundle"); 14630 let release_err = 14631 validate_generic_contract_bundle(&bundle).expect_err("release policy failure"); 14632 assert!(release_err.contains("publish_order.crates is missing publish crates")); 14633 let _ = fs::remove_dir_all(&release_error_root); 14634 } 14635 }