lib

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

commit f45235936b8e551422cab34fd1792d02e63fdcb3
parent 95a3d8cc215a8e5ed22197db59dd2d5354a8f239
Author: triesap <tyson@radroots.org>
Date:   Sun, 26 Jul 2026 06:47:06 +0000

events: separate semantic identity from release provenance

- replace mutable source-tree hashing with structured protocol, API, invariant, vector, and executor authority
- validate the publication allowlist public surface through the Rust AST and typed operation metadata
- add a closed release-provenance schema and clean-candidate collector for source, toolchain, graph, feature, and archive evidence
- reject dirty candidates, external path sources, incomplete archives, unsafe output paths, and cross-field identity drift

Diffstat:
Acontracts/releases/provenance/phase1_publication_release_provenance_v1.schema.json | 340+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/event_codec/contracts/phase1_publication_allowlist_v1.descriptor.json | 10+++++-----
Mcrates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.json | 2160+++----------------------------------------------------------------------------
Mcrates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.schema.json | 125+++++++++++++++++++++++++++++++++++++------------------------------------------
Mcrates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.sha256 | 2+-
Mtools/xtask/src/contract.rs | 5+++++
Mtools/xtask/src/contract/phase1_publication_allowlist.rs | 487+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Atools/xtask/src/contract/release_provenance.rs | 1483+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtools/xtask/src/main.rs | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 2311 insertions(+), 2360 deletions(-)

diff --git a/contracts/releases/provenance/phase1_publication_release_provenance_v1.schema.json b/contracts/releases/provenance/phase1_publication_release_provenance_v1.schema.json @@ -0,0 +1,340 @@ +{ + "$defs": { + "archive": { + "additionalProperties": false, + "properties": { + "byte_length": { + "minimum": 1, + "type": "integer" + }, + "filename": { + "pattern": "^[A-Za-z0-9_.-]+\\.crate$", + "type": "string" + }, + "hash_algorithm": { + "const": "sha256_bytes_v1" + }, + "package": { + "minLength": 1, + "type": "string" + }, + "sha256": { + "$ref": "#/$defs/sha256" + }, + "version": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "package", + "version", + "filename", + "byte_length", + "sha256", + "hash_algorithm" + ], + "type": "object" + }, + "command": { + "additionalProperties": false, + "properties": { + "command": { + "minLength": 1, + "type": "string" + }, + "purpose": { + "minLength": 1, + "type": "string" + }, + "required_result": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "purpose", + "command", + "required_result" + ], + "type": "object" + }, + "feature_profile": { + "additionalProperties": false, + "properties": { + "features": { + "items": { + "minLength": 1, + "type": "string" + }, + "type": "array", + "uniqueItems": true + }, + "no_default_features": { + "type": "boolean" + }, + "package": { + "minLength": 1, + "type": "string" + }, + "test_threads": { + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "package", + "no_default_features", + "features", + "test_threads" + ], + "type": "object" + }, + "file": { + "additionalProperties": false, + "properties": { + "byte_length": { + "minimum": 1, + "type": "integer" + }, + "hash_algorithm": { + "const": "sha256_bytes_v1" + }, + "path": { + "minLength": 1, + "type": "string" + }, + "sha256": { + "$ref": "#/$defs/sha256" + } + }, + "required": [ + "path", + "byte_length", + "sha256", + "hash_algorithm" + ], + "type": "object" + }, + "git_oid": { + "pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$", + "type": "string" + }, + "nonempty_unique_strings": { + "items": { + "minLength": 1, + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + }, + "sha256": { + "pattern": "^[0-9a-f]{64}$", + "type": "string" + } + }, + "$id": "https://radroots.org/contracts/releases/phase1-publication-release-provenance-v1.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "authority": { + "const": "candidate_release_evidence_not_protocol_authority_v1" + }, + "candidate": { + "additionalProperties": false, + "properties": { + "clean_worktree": { + "const": true + }, + "commit_oid": { + "$ref": "#/$defs/git_oid" + }, + "object_format": { + "enum": [ + "sha1", + "sha256" + ] + }, + "tree_oid": { + "$ref": "#/$defs/git_oid" + } + }, + "required": [ + "commit_oid", + "tree_oid", + "object_format", + "clean_worktree" + ], + "type": "object" + }, + "commands": { + "items": { + "$ref": "#/$defs/command" + }, + "maxItems": 4, + "minItems": 4, + "type": "array" + }, + "contract_id": { + "const": "radroots.release.phase1_publication_provenance.v1" + }, + "dependency_graph": { + "additionalProperties": false, + "properties": { + "command": { + "const": "cargo metadata --locked --format-version 1" + }, + "hash_algorithm": { + "const": "sha256_bytes_v1" + }, + "node_count": { + "minimum": 1, + "type": "integer" + }, + "normalization": { + "const": "cargo_metadata_semantic_graph_v1" + }, + "normalized_byte_length": { + "minimum": 1, + "type": "integer" + }, + "normalized_sha256": { + "$ref": "#/$defs/sha256" + }, + "package_count": { + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "command", + "normalization", + "package_count", + "node_count", + "normalized_byte_length", + "normalized_sha256", + "hash_algorithm" + ], + "type": "object" + }, + "feature_profiles": { + "additionalProperties": false, + "properties": { + "contract": { + "$ref": "#/$defs/file" + }, + "selected": { + "items": { + "$ref": "#/$defs/feature_profile" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "contract", + "selected" + ], + "type": "object" + }, + "lockfile": { + "$ref": "#/$defs/file" + }, + "manifest_schema": { + "$ref": "#/$defs/file" + }, + "packages": { + "additionalProperties": false, + "properties": { + "archives": { + "items": { + "$ref": "#/$defs/archive" + }, + "minItems": 1, + "type": "array" + }, + "publish_policy": { + "$ref": "#/$defs/file" + }, + "release_version": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "release_version", + "publish_policy", + "archives" + ], + "type": "object" + }, + "schema_version": { + "const": 1 + }, + "semantic_contract": { + "$ref": "#/$defs/file" + }, + "source_digest": { + "additionalProperties": false, + "properties": { + "algorithm": { + "enum": [ + "git_tree_sha1_v1", + "git_tree_sha256_v1" + ] + }, + "oid": { + "$ref": "#/$defs/git_oid" + } + }, + "required": [ + "algorithm", + "oid" + ], + "type": "object" + }, + "toolchain": { + "additionalProperties": false, + "properties": { + "channel": { + "minLength": 1, + "type": "string" + }, + "components": { + "$ref": "#/$defs/nonempty_unique_strings" + }, + "contract": { + "$ref": "#/$defs/file" + }, + "targets": { + "$ref": "#/$defs/nonempty_unique_strings" + } + }, + "required": [ + "channel", + "components", + "targets", + "contract" + ], + "type": "object" + } + }, + "required": [ + "schema_version", + "contract_id", + "authority", + "manifest_schema", + "semantic_contract", + "candidate", + "source_digest", + "toolchain", + "lockfile", + "dependency_graph", + "feature_profiles", + "packages", + "commands" + ], + "title": "Radroots Phase 1 Publication Release Provenance", + "type": "object" +} diff --git a/crates/event_codec/contracts/phase1_publication_allowlist_v1.descriptor.json b/crates/event_codec/contracts/phase1_publication_allowlist_v1.descriptor.json @@ -2,22 +2,22 @@ "contract_id": "radroots_event_codec.phase1_publication_allowlist_v1", "event_contract_registry_v7_sha256": "91595544310f865bdef064ee760c227c870417a95b87b3e27278f8da74fdddea", "manifest": { - "byte_length": 87388, + "byte_length": 10601, "hash_algorithm": "sha256_bytes_v1", "path": "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.json", - "sha256": "4b82348c001a25e855ab945e7d3191cd85925614b270e90fdbf85450a4a3dd6d" + "sha256": "8629b5c547e8f9daad473ab9d570b206d00db134cc22b4d8e81be10d0f3d10ec" }, "manifest_schema": { - "byte_length": 8441, + "byte_length": 9016, "hash_algorithm": "sha256_bytes_v1", "path": "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.schema.json", - "sha256": "9ffc63e0722948e600061cee2e2992f201a68e670b281e86a9447593ad3105f1" + "sha256": "638601348dece886ed9666251b5cf68d0a7f96c26dce115b65ed859a2db03d93" }, "manifest_sidecar": { "byte_length": 65, "hash_algorithm": "sha256_bytes_v1", "path": "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.sha256", - "sha256": "b12888e41621bcfd6fccc39e1e291a0f9fa6376aef9e983e357710871b36a28e" + "sha256": "4dd9bf6f230f02d8c2ca3c323e83fe9b77eb9f0895f708eb0949b7153b2fd6bd" }, "predecessor_manifest_sha256": "a07aace74f4747ba6e769a99acad7eadaac2d19d26aa0dd1c280ab92454519b5", "schema_version": 1 diff --git a/crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.json b/crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.json @@ -4,8 +4,8 @@ "authority_id": "phase1_publication_allowlist_v1", "manifest_schema": { "path": "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.schema.json", - "byte_length": 8441, - "sha256": "9ffc63e0722948e600061cee2e2992f201a68e670b281e86a9447593ad3105f1", + "byte_length": 9016, + "sha256": "638601348dece886ed9666251b5cf68d0a7f96c26dce115b65ed859a2db03d93", "hash_algorithm": "sha256_bytes_v1" }, "predecessor": { @@ -65,6 +65,45 @@ }, "evolution": "immutable_registry_v7_plus_additive_phase1_publication_allowlist_v1" }, + "protocol_sources": [ + { + "id": "nostr_nips", + "repository": "https://github.com/nostr-protocol/nips", + "revision": "bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91" + }, + { + "id": "blossom", + "repository": "https://github.com/hzrd149/blossom", + "revision": "b5bd2801d1763aa635fc8fea7a76597e0eb18990" + } + ], + "public_api": { + "constants": [ + "RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_VERSION", + "RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_CONTRACT_ID", + "RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_REGISTRY_VERSION" + ], + "types": [ + "RadrootsPhase1PublicationLeaf", + "RadrootsPhase1AllowlistedPublicationArtifact", + "RadrootsPhase1PublicationAllowlistError" + ], + "functions": [ + "allow_phase1_publication_artifact", + "allow_phase1_publication_canonical_json" + ], + "methods": [ + "RadrootsPhase1AllowlistedPublicationArtifact::artifact", + "RadrootsPhase1AllowlistedPublicationArtifact::into_artifact", + "RadrootsPhase1AllowlistedPublicationArtifact::leaf", + "RadrootsPhase1PublicationAllowlistError::code", + "RadrootsPhase1PublicationAllowlistError::source_code", + "RadrootsPhase1PublicationLeaf::as_str", + "RadrootsPhase1PublicationLeaf::authored_operation_id", + "RadrootsPhase1PublicationLeaf::event_contract_id", + "RadrootsPhase1PublicationLeaf::kind" + ] + }, "allowlist": { "version": 1, "contract_id": "radroots.phase1.publication_allowlist.v1", @@ -130,6 +169,14 @@ ], "event_policy": "strict_typed_nip52_date_or_time_artifact_only_v1", "classified_listing_policy": "raw_marker_partition_before_focused_food_profile_validation_v1", + "invariants": [ + "sealed_artifact_revalidation_v1", + "closed_seven_leaf_inventory_v1", + "kind1_ask_then_photo_update_then_update_precedence_v1", + "strict_typed_nip52_date_or_time_v1", + "classified_listing_partition_before_food_validation_v1", + "excluded_product_families_fail_closed_v1" + ], "denied_families": [ "unsealed_profile", "unsealed_calendar_event", @@ -180,2101 +227,18 @@ "tools/xtask/src/contract/raw_source_rebuild.rs", "tools/xtask/src/main.rs" ], - "source_files": [ - { - "role": "cargo_config_authority", - "path": ".cargo/config.toml", - "byte_length": 37, - "sha256": "7cf5642012b512304c8b503e13fba165873fd33572c1e97f4109ae8796def384", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "release_notes", - "path": "CHANGELOG.md", - "byte_length": 32509, - "sha256": "8befc8d955998cb45c262ce122b5d982323ced779f1162d7f5cdd5c55845ecca", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "workspace_lockfile_authority", - "path": "Cargo.lock", - "byte_length": 218980, - "sha256": "c2759e01b642d7da0988e087079702846bfe7a518e6d968d2f0d365a1787dff6", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "workspace_manifest_authority", - "path": "Cargo.toml", - "byte_length": 10987, - "sha256": "3be12d5ebd71164a678121b4ca76f724718ae81fbf6b62281d47b6a36bbfdda6", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "operations_authority", - "path": "contracts/operations.toml", - "byte_length": 80259, - "sha256": "8bb1c542379533eff3a4aaed8cef2bba9234b0fb03739554810ca1019caab6e9", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "release_authority", - "path": "contracts/releases/1.0.0-alpha.1.toml", - "byte_length": 22713, - "sha256": "8c4aa7e4142aeff62f21d34906ef08ea36bcb206544ec154561906526fd26604", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "blossom_manifest_authority", - "path": "crates/blossom/Cargo.toml", - "byte_length": 1118, - "sha256": "f993430f919e86e6bd97289db0428959960c1f4d01ba2bd77c23d233f69d25d1", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/blossom/src/authorization.rs", - "byte_length": 44288, - "sha256": "319eb9a5de6389c539d164a985900730b886685ebcac0382b803d8249a38bef6", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/blossom/src/descriptor.rs", - "byte_length": 16107, - "sha256": "64bc09a878cabd5ea327f8b86a1c18a7d1e6a5e686c0441c899a5d0790702532", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/blossom/src/error.rs", - "byte_length": 30918, - "sha256": "bd77810306b3556434d93057ca5ee62db474e9b0af94176ba4aa7a2ef25be7d8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/blossom/src/hash.rs", - "byte_length": 11251, - "sha256": "753eff6ef9a810045c88839d39a46b37d62a4ad0a5ea57aa30e2a8219e3cdbda", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/blossom/src/lib.rs", - "byte_length": 2172, - "sha256": "97cae38f693795445cc17671649f3d88c0c492fc8d71d2c98a4ca02502e5d43a", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/blossom/src/publication_readiness.rs", - "byte_length": 69610, - "sha256": "77fea26e0d74cc4064ec4e4916a4e61be251df9d4c0d42ac5c98a7e8faeeea3a", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/blossom/src/publication_readiness/sequential_jpeg.rs", - "byte_length": 44379, - "sha256": "22b8704466887a892f00469db895ace1bb780c48849e3bfe6d7d94682ce366d8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/blossom/src/url.rs", - "byte_length": 15794, - "sha256": "e9673f074ba6328a121aa3008fc11cd4d9d22cae3b09f26602ea6fea3f964c80", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "core_manifest_authority", - "path": "crates/core/Cargo.toml", - "byte_length": 982, - "sha256": "2f386a9a0a87acfcbd8e39dc4c78af30d0d6c3855689194ff0355d8d730713c9", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/currency.rs", - "byte_length": 4261, - "sha256": "94f4ac6ef4ac0957c86b66a4910390b36c09faad9c05162a64220969cf263861", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/decimal.rs", - "byte_length": 3895, - "sha256": "84c4fcd5d0979c5c931ac84096b8eb35d9ac842d853df07af5da42aa2c813105", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/discount.rs", - "byte_length": 2439, - "sha256": "66406ccfc3662519d6bcf7737818e39ab8cbb322f384cd3cfbbabf7f7a8c3fb8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/dto.rs", - "byte_length": 1151, - "sha256": "77a2ed8df26459f117262ef86f93f80718a54eedbdb583ffd55f25517743e32b", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/generated/dto_roots.rs", - "byte_length": 1163, - "sha256": "a611e8712acd07098bd088f050f4112293b5014c574e4211406dc12dcb90b57a", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/lib.rs", - "byte_length": 1177, - "sha256": "56ae7544e668488533ede4a9573d710bed5f1e125811862d82bc6ba914854041", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/money.rs", - "byte_length": 6812, - "sha256": "2194a9777d8684a751345236afe48647176b451fb695e89fc13f2f18dd030055", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/percent.rs", - "byte_length": 2373, - "sha256": "d109bae54bfb9469134e044064406d10c395ea9d3604eda1ca8ab10b83ae9575", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/quantity.rs", - "byte_length": 6698, - "sha256": "668ee66ec23917c74d2d5ef494f966cbbfe7417a4f17eb1a239c49d86dfa89e8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/quantity_price.rs", - "byte_length": 6207, - "sha256": "0f8945e17638ba5dc6da6bb127f4e35b67433354dad8645c5b958262bdf32eed", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/serde_ext.rs", - "byte_length": 755, - "sha256": "fe2866f1d0aa659dd4f5a45c7c30fd0e509993112ef5fa7b1502c2d88d92249c", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/core/src/unit.rs", - "byte_length": 10255, - "sha256": "cf248951bf90bcdf75121a8e4dca6415ffb976ff4eead7c453f24bdb700fbf1a", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "event_manifest_authority", - "path": "crates/event/Cargo.toml", - "byte_length": 1711, - "sha256": "e6e9b57dde0f3cea4a67adfe128ecfbf5ac727488efdf819323a8008bb2b2fa6", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/account.rs", - "byte_length": 539, - "sha256": "ae3d3ed5bf8096c35fb065b1cd35bd281a7dd6d10085df235e65fb1b178ef1fa", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/app_data.rs", - "byte_length": 506, - "sha256": "cea3f71921f5a5e092024715b6e9b07aff11dfb6f28bd83a0998e293509c29ce", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/article.rs", - "byte_length": 2247, - "sha256": "99b0ab0f70b46415685f2d5de73010ff027335194d0a7acbed15bee337f0e167", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/calendar.rs", - "byte_length": 103971, - "sha256": "e6f090246aecc94cb107eef8951e926324d7216b30bc83dcf8a6ad2e6bd508d6", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/classified_listing.rs", - "byte_length": 8226, - "sha256": "745aa08953a6243ea1afad22aa63e44f6746f2f243ab0d6b617d6ab64fa58749", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/comment.rs", - "byte_length": 36170, - "sha256": "5bfd3c8ddf77ef77b61eebb0c50f1d3384e7724a55d18f1b2c06bbb32b5c7c65", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/contract.rs", - "byte_length": 86, - "sha256": "9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/contract/registry_v7.rs", - "byte_length": 155836, - "sha256": "7100b12838e8a65e5a0e8152c999efbafef38d54944cbb2edc4c3ceedc1c1b55", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/contract/registry_v7/tests.rs", - "byte_length": 67724, - "sha256": "113b4e7231abc5a00e7d68214aec09ad44be2c839ecaeff9678e4d34f7487241", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/coop.rs", - "byte_length": 1400, - "sha256": "15ca2fba78362fde9e2a6b584880112737302d2cc666102159b4cc1bd3044415", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/deletion.rs", - "byte_length": 31019, - "sha256": "d04ac21d395e3607b61aa1b0ec86af8e114fbe4b3ebb6fbb596bcb51a0354996", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/document.rs", - "byte_length": 974, - "sha256": "a7af3be1a948c7be163b7e720cdbfb20d60142b3def0f2e31ca391a054dd1419", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/draft.rs", - "byte_length": 59007, - "sha256": "0bd517ba0969ca253584c6bda4b08494b63012463a259750ebfb81443a5ce4bd", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/dto.rs", - "byte_length": 5222, - "sha256": "72272da20bd529ef89a1c7e761f94d53f9e1a333ab1f12bee8f3fe7ee7d0b900", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/envelope.rs", - "byte_length": 31243, - "sha256": "3a9bd5cc07ef4573ae132d848cea5d3f18f61137b132672f9088cbe1a917f6a2", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/event_head.rs", - "byte_length": 68, - "sha256": "cb52f6006f7ecd862707d6b048f9fc407e0cd5ebcd3091b3c54e195ffa5cba64", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/event_head/v1.rs", - "byte_length": 7574, - "sha256": "fb52339def559f01f43f2f02dd142da192cd7b7074e116783f56a2b6009fddfc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/event_head/v1/tests.rs", - "byte_length": 12218, - "sha256": "25aea7d0f9bc170c5f94d786ac855dc7373c76e17e755cf42baf454f6f9fdfa6", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/farm.rs", - "byte_length": 1341, - "sha256": "52c67d44ebcfb4c9716fb4e47bf4268b0a1734c2255fb6bfd2fcd5912ec06aba", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/farm_crdt.rs", - "byte_length": 23010, - "sha256": "50f629a6b98063ae02fdd2f10d7e6bcb89908815b223dceb7cb27d9d8501aa51", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/farm_file.rs", - "byte_length": 4988, - "sha256": "11cc5fbe20d7cb3a1d656d62448ce5d343a6c65c34e0119d714ac905cecc16be", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/farm_workspace.rs", - "byte_length": 5441, - "sha256": "15439d472f89cf9b2c37de035d9ba24a0c74498b0fb3704f6766030b5ea60c9f", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/file_metadata.rs", - "byte_length": 3567, - "sha256": "e58477754386bd5029ed97ce37f5459613692abb9fc484432682103d4583eca7", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/follow.rs", - "byte_length": 844, - "sha256": "ef622d7d549e6566d632931bf63b0cca2a775328bb708df345410f586111f170", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/food_availability.rs", - "byte_length": 45680, - "sha256": "1c8c3fba6514f9b246545b3305e8ac2742258857d1b813fa34c5fbefa6135e12", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/gcs.rs", - "byte_length": 1752, - "sha256": "c2769982fb3798922998fb4c73eb9bc24809f00d2e274157eda9b2512458d69c", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/generated/dto_roots.rs", - "byte_length": 11201, - "sha256": "bba7b3af6a4bde9479ca640923a849665739a238e5019f0e3f68e462b99161ca", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/geochat.rs", - "byte_length": 458, - "sha256": "d8f3393aeacd335adb06a8c292e0cf183b352d1e14ad7ed7538c5ccc651d891b", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/gift_wrap.rs", - "byte_length": 793, - "sha256": "fac8c4e9ae3704228d41bd0b6d5220a8f2ab2406c73f6683514f1cc8dd0684bf", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/group.rs", - "byte_length": 10479, - "sha256": "ce7576787aba1c205b0486dd941efb7c9870b78fb969176c0e2a4f292cc80fab", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/http_auth.rs", - "byte_length": 1614, - "sha256": "81f9bd16ecd62f5ca2f5b3f9101a8abc479c71f33ec1202a28ab505d883b94b3", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/ids.rs", - "byte_length": 45525, - "sha256": "9f6a893d1f9523157e70cf5a0fd5fde8221c7a518a130b2924de67c04f8eecb2", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/job.rs", - "byte_length": 1887, - "sha256": "99a4a1926a50a56c1da40721332abc7ff34b79809255f2d13727ed82b9ddc597", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/job_feedback.rs", - "byte_length": 704, - "sha256": "c19334328646115d10d66fb19bd5c8082e20363b0f62384dc7c3fee861b41c89", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/job_request.rs", - "byte_length": 1387, - "sha256": "40080d798b75a127e304ea93c89cd6e471a2c6139745b94708dd50bcaeb050a7", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/job_result.rs", - "byte_length": 719, - "sha256": "f8abb7d5f832f1e86a3b1b5ad20b0e5f8099902307850c04b17702a709ff0b21", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/kinds.rs", - "byte_length": 34961, - "sha256": "8b3ce6193cab1f7e1587d0c1b2880a81b0aa43c77d89671b602772db37edcebf", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/knowledge.rs", - "byte_length": 56561, - "sha256": "f043b448f781d10330e065e6b3df77f9000434a02745d2590f839fca6e7a6649", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/lib.rs", - "byte_length": 2746, - "sha256": "ae2a43690d44017565dfbc86af5033a60cdb39a5f32440956c8e0325accc5120", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/list.rs", - "byte_length": 1636, - "sha256": "0bbfde90ac3ea769ae9b603f78654a8dd6f436e35cab88f86410e10946d30d59", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/list_set.rs", - "byte_length": 560, - "sha256": "84b1227413967f8aa491996527469a9bd83f2186b294c20415f26e9116d5a537", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/location.rs", - "byte_length": 1932, - "sha256": "f2fcf688c66e9798c20cf7737d1f9d0c4f9bd030cbfeedc1375c6c6fdfd2d120", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/media.rs", - "byte_length": 3821, - "sha256": "ca98ddb134cb522912900544080d910867398d6aff6e8c6b061dc1fb77212ec3", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/message.rs", - "byte_length": 882, - "sha256": "b8d513067218a4848d67f316708448549871608752b666ccae87adf1652c912a", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/message_file.rs", - "byte_length": 1380, - "sha256": "b4f0b4f9a641a78f4400bdbbe7c857d814cf5d20cfbb56aca1f09e80da975473", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/operational_listing.rs", - "byte_length": 10125, - "sha256": "34ff16458fd9d5e19e80b49fcf80f41903081f19056ab4e50ea4af6665af3acf", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/order.rs", - "byte_length": 64764, - "sha256": "edaf3e270dcffa6ab33bba206fa0aaf4480f093f5e03b97b70af0543e78ae96e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/order_economics.rs", - "byte_length": 5749, - "sha256": "f4c05cdcdeea77f88052b56807d687b16b2a1c8cf60f0aef9b5770d194678240", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/plot.rs", - "byte_length": 1333, - "sha256": "47cef6cee21d3ea915f1eb4ebe47d15a70270801e7b49b05d91def6062a34142", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/post.rs", - "byte_length": 22813, - "sha256": "d4a573cf2510f3a261f6aac3efd16489591281cdbdb387af5a7d67f516e1e056", + "result_vector": { + "canonical_path": "contracts/conformance/vectors/publication/phase1_allowlist.v1.json", + "mirror_path": "crates/event_codec/tests/fixtures/phase1_publication_allowlist.v1.json", + "byte_length": 49975, + "sha256": "2867ee401db8cfad3a77869847c57567e869623f55d3d6c9e98a7fa0a643c3d6", + "hash_algorithm": "sha256_bytes_v1", + "executor": { + "path": "crates/event_codec/tests/publication_allowlist.rs", + "byte_length": 7562, + "sha256": "342d3d3d1100cb5d31aca94b0540b1e03761b034de23475bdb5142baabeed8fd", "hash_algorithm": "sha256_bytes_v1" }, - { - "role": "public_production_source", - "path": "crates/event/src/profile.rs", - "byte_length": 19061, - "sha256": "f2b03be027aea187645e8bc3ae7a7d4004d6e58d4e1b361b35786ccdc36d907a", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/reaction.rs", - "byte_length": 429, - "sha256": "ad0ce1e9bef6b0d20665c0c69bfee81819136786b8f3cee468815b9ba70b95cd", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/relay_auth.rs", - "byte_length": 1099, - "sha256": "1c4ae98333a22889f76d2143167054fb48265e0148a241fcd9c5fd549836ba9f", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/relay_document.rs", - "byte_length": 1394, - "sha256": "5e0abdcb0d39f5bbebce331fd96c64678cb1852e195e82481732f6057aad7b1c", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/relay_hint.rs", - "byte_length": 14317, - "sha256": "1a14aea5d2f2620970dc1de9e3d311d9042cab4d5805a56461548168112a0801", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/reply.rs", - "byte_length": 16026, - "sha256": "07979efc36b8096984c08908655ea7332aca9a9bc28857a4e5adda28f704d363", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/report.rs", - "byte_length": 2274, - "sha256": "5324a57e4cebc8da10ebb746ab91b216d473421465c860a0cd2e3b4396929d77", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/repost.rs", - "byte_length": 1942, - "sha256": "e56ce06bb11d46eb579f33561d6bafe0e30d44922f34c58708ba0cc05363edf7", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/resource_area.rs", - "byte_length": 1326, - "sha256": "a3bfa00639b1c5408e7508da1e331f37554ad303adb12ed2d9257aee0c470859", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/resource_cap.rs", - "byte_length": 1354, - "sha256": "a8b0eac266c7b85fa6a86e54c5ce85de75e813498ab19df269abc997c09f0d33", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/seal.rs", - "byte_length": 370, - "sha256": "1cfcaff0e7cc310aa4c903b35260efbec53c81080f8ad5bdf57e03375f7bd673", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/social.rs", - "byte_length": 5468, - "sha256": "e619ac41041c7e03d14fff95664cd3ec77be181ce9954d76c28daf1fcc28b3d2", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/tags.rs", - "byte_length": 4530, - "sha256": "cb8f12a639fc72d9238e32495d9e67e928e6627fb8c94ec0a58c5d95e7373cd6", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/trade.rs", - "byte_length": 68364, - "sha256": "33f34933a75459314c721ca65631a0a7b2493439bd638a10ee3e103d0845fbfe", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/trade_validation.rs", - "byte_length": 4794, - "sha256": "430ebb19ff1399e09213f720521e5f9ec467c63662aa25e870584486079d6b61", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/wire.rs", - "byte_length": 68, - "sha256": "cb52f6006f7ecd862707d6b048f9fc407e0cd5ebcd3091b3c54e195ffa5cba64", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/wire/v1.rs", - "byte_length": 22136, - "sha256": "4c65e240be61246bae91ca3223ec93fe8efdd66324e8c198207fd993896044d7", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event/src/wire/v1/tests.rs", - "byte_length": 18595, - "sha256": "7b31012f1033af75b77c61ca9895e52f660e206a103fe8cf5a84cfbe00cb82f4", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "event_codec_manifest_authority", - "path": "crates/event_codec/Cargo.toml", - "byte_length": 1772, - "sha256": "e0c227e10a3bc2aedd6f3ea676dff0100b5ac52847df01a404cf2ac7c5d11a6b", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "event_codec_documentation", - "path": "crates/event_codec/README", - "byte_length": 22543, - "sha256": "8b09d3b8e39387b10a4a676fe2c5ed7d56cc31af79bdb8edf88f66a8f65e2c69", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/admission.rs", - "byte_length": 20666, - "sha256": "aa51f9e571eb80ade307cf9069a42ac7adfdc4c4abe5da1a2f6d4a7192c7ceea", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/admission/registry_v7.rs", - "byte_length": 5350, - "sha256": "755e63dd7ad1115c219e5a3ea540bef67d2770fc9f2a5aa6ba97c925c99bdced", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/admission/registry_v7/tests.rs", - "byte_length": 834, - "sha256": "d24359ab6004a316725ccfe448f2cf4e422f918ad4128e37358fa84bee069a0f", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/app_data/decode.rs", - "byte_length": 2058, - "sha256": "60cf53efb646c04dd2927b3f5e6fa637f2b856af756f9e79d49b6b3743cdc7b6", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/app_data/encode.rs", - "byte_length": 1150, - "sha256": "529c6d828ebfe2d0f0b629c94983f3f5577629069d8a5cbc6b3b49355aa538f4", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/app_data/mod.rs", - "byte_length": 57, - "sha256": "5c57058e78fa54008ba63a97ba12aee00db28753fa39bdff3cb7347e3447dabc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/article/decode.rs", - "byte_length": 3346, - "sha256": "ace004ba624b3e69629f9098b18400c42eb4302b248742032a46b97397b52dfc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/article/encode.rs", - "byte_length": 2328, - "sha256": "b3c67259750aa38431c2a7e17c9baf4a4db6703e13ed9af2df897c4b0c10a1dc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/article/mod.rs", - "byte_length": 32, - "sha256": "f72b49a09f84d980791360980c31534c1058bd90ba181ba7e97d871663998c0d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/calendar/decode.rs", - "byte_length": 23117, - "sha256": "9416fc8b061d230ab5c36877f70a6668fc75a171ac2005286a7cc21878fcd62e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/calendar/encode.rs", - "byte_length": 10162, - "sha256": "fa71aed54f24c003bffb7b860b0864033b1f3c9cbc87ab10b04e346c92ee55c4", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/calendar/mod.rs", - "byte_length": 32, - "sha256": "f72b49a09f84d980791360980c31534c1058bd90ba181ba7e97d871663998c0d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/comment/admission.rs", - "byte_length": 4176, - "sha256": "32eb486d43790a92d2faeed6684396f77aef33061d4a56bd848e0925c5c385f0", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/comment/authored.rs", - "byte_length": 11756, - "sha256": "ff93c671d3053716abe9cc0db849c61eeabffcf19de10169d94f7440ddfc574b", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/comment/inbound.rs", - "byte_length": 86, - "sha256": "9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/comment/inbound/registry_v7.rs", - "byte_length": 53887, - "sha256": "e540649f129901fa262e15a5ee9318c4ca70dbd8ec6ff0ff62a4ddb94750a225", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/comment/inbound/registry_v7/tests.rs", - "byte_length": 11799, - "sha256": "afb4ccd66aeabc7029246241f107b7f729a03c399f0c8e274c3e2530b090812c", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/comment/mod.rs", - "byte_length": 54, - "sha256": "986f59844194fbaac78618c6131940691d49dc888702366b3520c901a08990e0", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/coop/decode.rs", - "byte_length": 2512, - "sha256": "70ed255179e781417a59d4d2567cd88fe488ccd1eaca6b6e2df8bebee6c7f969", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/coop/encode.rs", - "byte_length": 2820, - "sha256": "19ac6c780f27eb691375dbcd438f4b24037f95ef7af05c3ccfdc67542ca25bba", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/coop/list_sets.rs", - "byte_length": 11371, - "sha256": "d626bd31d764c0eba3ded920f6973de2eb18e56e92ce747068e5a067cbc41fe3", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/coop/mod.rs", - "byte_length": 7095, - "sha256": "1a78f8e91d88d754266798c8312801a0f910fb146d28433aa0311b2c3cceff5f", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/d_tag.rs", - "byte_length": 1880, - "sha256": "65d41e1b2e799fa036a32f587176d6f321319cff2bdc469875f009014e4b96c6", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/deletion/admission.rs", - "byte_length": 73, - "sha256": "2a652ac0ad4f1b99078b7dc361bc2d2e0dc29a1f0343aedb35a689a5388fd3f6", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/deletion/authored.rs", - "byte_length": 6260, - "sha256": "3d103b07afa12119771d87326d10dc8f2393abda96b88146c0e87d5c05c71471", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/deletion/evaluator.rs", - "byte_length": 73, - "sha256": "69b00d69fbae5bd5c1384c5ac926982656ffea34f826f5affb75ddd5672bb333", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/deletion/inbound.rs", - "byte_length": 71, - "sha256": "e502eccc04cf9c53aa54372cc48e1521e2de42bf58669f653b293bb10eccd00e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/deletion/mod.rs", - "byte_length": 115, - "sha256": "17d56f82ddb0a86bc97abbcf3e037fe460ea31fff2548f527be4bf004dac3a95", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/deletion/reconciliation_v1.rs", - "byte_length": 38491, - "sha256": "a3e23142c65c5c972b117fb65492e1e49755c3ada95ced4421161e0748116d42", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/deletion/reconciliation_v1/admission/tests.rs", - "byte_length": 2292, - "sha256": "50bdf2ca5a2a63ca84afa4485554045237a91aa13cda1cc0fcb9cf9e02dddbc0", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/deletion/reconciliation_v1/evaluator/tests.rs", - "byte_length": 16583, - "sha256": "0c33fb1eb3ece09889f1d9f25d042a2012d851b462a94ee56619e96bc2dac6e2", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/deletion/reconciliation_v1/inbound/tests.rs", - "byte_length": 14354, - "sha256": "c7bef0de70e98c0535aba83b4be0840b912c14ac11ec9a1156be35a451a2dfcc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/document/decode.rs", - "byte_length": 5731, - "sha256": "986f0333047ade2a0b1fd598c3498a4170121bd122ec888d7ac19d7af5d7c457", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/document/encode.rs", - "byte_length": 4577, - "sha256": "5224d82236b99b46b67708435d56a08ef988b6bf109778b0b16925f6a69bfbb4", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/document/mod.rs", - "byte_length": 1266, - "sha256": "6f4acb5c28649aa62a8f69cc2d67c8dbc012fd98f307ba02efed9d2879d64c37", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/error.rs", - "byte_length": 3668, - "sha256": "786e1fee6e0c181610cac49e26d313ee99ae995968aedf29090d829c738b3731", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/event_ref.rs", - "byte_length": 5841, - "sha256": "ba6eb777bb904abe0af5cf9e7cbab80e9a17a951eeac9a11b3f5a0a1ee1c0f38", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm/decode.rs", - "byte_length": 5353, - "sha256": "d81164d8d94116b68009875fa20f7473ad0fe47bd61c5aada8c058377996008a", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm/encode.rs", - "byte_length": 3262, - "sha256": "20cea8aa2d56c3ede56b9539409f3b650d03310897b08eb8bdcfd3c0e0ee632c", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm/list_sets.rs", - "byte_length": 9511, - "sha256": "dc2e9e2b5a3521dbd5ebb8c0ee7e12ceeab94aa81fc0bb1382d5d5359b52c27a", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm/mod.rs", - "byte_length": 19663, - "sha256": "70e1ebf5d9432288d3ab6dc2c1491eecaaa52d352fa7888ba0e662a96fa88490", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm_crdt/decode.rs", - "byte_length": 5310, - "sha256": "030fbb031a6eb103fbee8c67778af866e8dec61d5599a6934b002f9ff9b299ed", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm_crdt/encode.rs", - "byte_length": 4298, - "sha256": "1c7ed28ca74a5b4722cb159e89e21bc78776e8a87258a1c8973caae9f0c25277", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm_crdt/mod.rs", - "byte_length": 25842, - "sha256": "9f46145cef77562e174dce30dcd16ec843fd519c2172961ab9e21f45dad715be", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm_file/decode.rs", - "byte_length": 10354, - "sha256": "d6858907bb2d5540c63e2a1a3b82763534189adf940270e2db1e0d974282543d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm_file/encode.rs", - "byte_length": 6395, - "sha256": "bc1194186acfe6a53ed43f3fb9c907e8a15500e5a14955262fc5890e54d5d4a8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm_file/mod.rs", - "byte_length": 18929, - "sha256": "a1fdfcdd010887b319000a487371aba5b318a2efc477b7ade80615d664f9ef8c", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm_workspace/decode.rs", - "byte_length": 4739, - "sha256": "11f999e3f49b39e83882e789f9273d34051268f836705c68815a46acd0da50cd", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm_workspace/encode.rs", - "byte_length": 3638, - "sha256": "4c60fc3fe7639e4d48e10b2b1ac49375ce49683e8d6fcf6e83273e5f5894fadf", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/farm_workspace/mod.rs", - "byte_length": 19261, - "sha256": "a29f42b0f4dd4e5330bf7b29f0877165f4509f173e3d564925b1cbc87e8ddcf8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/field_helpers.rs", - "byte_length": 11763, - "sha256": "abbb5b777e6b6b2b58aadcccab27647ba830c287f503c27fbfe42998a33e4d6e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/file_metadata/decode.rs", - "byte_length": 5033, - "sha256": "62573603394d8586a050c2e443185b079e4d56c22d58e8a04b41489b159c43f8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/file_metadata/encode.rs", - "byte_length": 3753, - "sha256": "6abf5c981e77235ec3c454ad1be9be745ab1448da07a53074d3fbebc73699372", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/file_metadata/mod.rs", - "byte_length": 32, - "sha256": "f72b49a09f84d980791360980c31534c1058bd90ba181ba7e97d871663998c0d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/follow/decode.rs", - "byte_length": 2726, - "sha256": "912fbb67053d766c2ff78993eeed05e9b72a0ea8c7424ec1d83026060306dce0", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/follow/encode.rs", - "byte_length": 5928, - "sha256": "ab3245bca4132ea129db60206aff9f1b510d0a633704da4c2bee21c3a31f234e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/follow/mod.rs", - "byte_length": 32, - "sha256": "f72b49a09f84d980791360980c31534c1058bd90ba181ba7e97d871663998c0d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/food_availability/admission.rs", - "byte_length": 5799, - "sha256": "727ebf8f086c14376aba745a0a02b269115fed01e8ce95f90fdd211b9640f842", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/food_availability/authored.rs", - "byte_length": 10125, - "sha256": "57c4c4342a6c37f1d02fb16384396274e2bf0f7f588321564bbb918c0d498822", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/food_availability/inbound.rs", - "byte_length": 86, - "sha256": "9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/food_availability/inbound/registry_v7.rs", - "byte_length": 26865, - "sha256": "908015346eaec97bee6f44fe2a524410b701529ac19a5dab4505706d40d527da", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/food_availability/inbound/registry_v7/tests.rs", - "byte_length": 6112, - "sha256": "4c5293e22009a768abfbba056686b15ad07558495cc83b071b979e269bb37637", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/food_availability/mod.rs", - "byte_length": 72, - "sha256": "7ef96c5ac35a698bcc02bc8b083a5972aaceb9a0e72ba085e68f3de2639f10f8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/food_availability/revision.rs", - "byte_length": 3881, - "sha256": "62cee7fe83f9f9ccf98981cda5fab63b70e98736b16f7afbee5e2c5725ef5e7c", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/geochat/decode.rs", - "byte_length": 3357, - "sha256": "e8ae9f749812fa2d712cb15c17638379e67dd49ae60ec347712ddbf0efa13fe0", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/geochat/encode.rs", - "byte_length": 1799, - "sha256": "6ecbca2ba32d9b8d91efa381dbd8ead122320dcdf421199287a964cfc15bc89d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/geochat/mod.rs", - "byte_length": 57, - "sha256": "5c57058e78fa54008ba63a97ba12aee00db28753fa39bdff3cb7347e3447dabc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/gift_wrap/decode.rs", - "byte_length": 3125, - "sha256": "0ccea7238978d14a669f2f39c76b353c342559e2c8ca9e9d325fa5174f535d92", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/gift_wrap/encode.rs", - "byte_length": 2051, - "sha256": "86d34554d225a77cb0bf625d32be25b3233f8267c693cb0551c62ac5d595caff", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/gift_wrap/mod.rs", - "byte_length": 57, - "sha256": "5c57058e78fa54008ba63a97ba12aee00db28753fa39bdff3cb7347e3447dabc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/group/decode.rs", - "byte_length": 10828, - "sha256": "9bb5d7898025f7a8dabeb223c721b33ad16f4faadf30793cc1a862fd849c1480", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/group/encode.rs", - "byte_length": 11784, - "sha256": "4565d50cc4ca225983283a5ae7fb0465b64dc8302979686ea3669dcbdd13cf1b", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/group/mod.rs", - "byte_length": 24171, - "sha256": "a6356251c7bfe0ae16555e97ac029adde2a22401fbb3ed3da776d3c463ca2cd8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/http_auth/decode.rs", - "byte_length": 2106, - "sha256": "fb9b0912f9d467c0b34d3293f67347333eac3cd645b30409c512d28e82f1bd52", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/http_auth/encode.rs", - "byte_length": 1530, - "sha256": "292c687b0b4df4bb331d4dca8804f2f058fddd334cdc3b0973078b0b006cf237", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/http_auth/mod.rs", - "byte_length": 8207, - "sha256": "9eedfd01a4ee7e44e1be80dd497cc0f82f6399bf6c8baa6f7d44933f0665f16f", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/encode.rs", - "byte_length": 1676, - "sha256": "75b1f0bce46524d2a1a0e7bffabbf86d08518d7ee4c69efd44b4a23881562651", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/error.rs", - "byte_length": 1578, - "sha256": "aa4deae1ae26130ef092b5863309c9b070b00fed98855b0bab2d0cc136feb915", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/feedback/decode.rs", - "byte_length": 3261, - "sha256": "0aec9d35ce4d4879c6ad2d6cdee9cf743642c0d74594a6dd481a7e10352b653e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/feedback/encode.rs", - "byte_length": 1853, - "sha256": "00a48ba2cda580fcfd5a831a52d36372ce975897e6ee84f52c53f8048adf306d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/feedback/mod.rs", - "byte_length": 32, - "sha256": "f72b49a09f84d980791360980c31534c1058bd90ba181ba7e97d871663998c0d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/mod.rs", - "byte_length": 112, - "sha256": "6428863fbbd2e08e18e6308cbbf1bb73ab92e4f2772933bc0011f91f3d5ef2a4", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/request/decode.rs", - "byte_length": 2778, - "sha256": "d8cc34984390a8e0a29db46b3b94baabeb5a89dd1ae1fb8a425fbf7f5f55a148", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/request/encode.rs", - "byte_length": 2408, - "sha256": "11b77bbb3eed9b36bdc7b6f9055ff2228864dc364fda78267f75d0411c9bb7b2", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/request/mod.rs", - "byte_length": 32, - "sha256": "f72b49a09f84d980791360980c31534c1058bd90ba181ba7e97d871663998c0d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/result/decode.rs", - "byte_length": 3083, - "sha256": "c38d16fa824c3320c7ebd5e0de6eb0eebab4c8265fc28911b8e60337eba04938", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/result/encode.rs", - "byte_length": 2366, - "sha256": "37b71cdebf674c8ca8afd0956361054d79e981d07e0285b5987f48554a9f4f8b", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/result/mod.rs", - "byte_length": 32, - "sha256": "f72b49a09f84d980791360980c31534c1058bd90ba181ba7e97d871663998c0d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/traits.rs", - "byte_length": 5037, - "sha256": "462db579a3ca9df48982c152c27a0770d5c5e5f4fb8b2b875b5a60bc03fa6588", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/job/util.rs", - "byte_length": 7101, - "sha256": "5f8a3d59e5fad7bcfe9694f028e8803e6ca523adc909e7f487ec7e6acccd77ab", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/knowledge/decode.rs", - "byte_length": 24681, - "sha256": "eecdf6fc09a4d9b4a5aade9f05d8d9b1028707802df7e5453dad76d4feb3ba6d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/knowledge/encode.rs", - "byte_length": 14873, - "sha256": "19bb647940c47d0e8fcf468af98b3ac7c101ee2b8b8db4b7f66dd900a80c85ee", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/knowledge/mod.rs", - "byte_length": 1197, - "sha256": "d18ed0e257f8e1e31cebd155f1fffb469a1bb6922c11e4088c762c38fee9e494", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/knowledge/verification.rs", - "byte_length": 7212, - "sha256": "7c9b078814dd1694876fb1231c6bdd6c57fbb3e8aeb7c6d26b32571255446176", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/lib.rs", - "byte_length": 2556, - "sha256": "f844cd20eefad594ede4fff0122b6d72081a68c15afefba7cc926b23820fcdd8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/list/decode.rs", - "byte_length": 3787, - "sha256": "c1e10e7a9fee7f2cf9a2c8ae1f8f49d1d05a4e6891c775fff0e08799fc8f9b31", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/list/encode.rs", - "byte_length": 3176, - "sha256": "eaac989d2983d1be6b528a91132489159d39adce0c273cc127e245fdbcd85da0", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/list/mod.rs", - "byte_length": 1913, - "sha256": "85425ed97d83ce82e7f845308475359a7ffcf33a54f05e0b34636c6c9833df76", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/list_set/decode.rs", - "byte_length": 4090, - "sha256": "3f495165a5b40274b732a4238784320bed46d054451d4afbb6e8eb3f4dcb197b", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/list_set/encode.rs", - "byte_length": 2772, - "sha256": "5d78f890610c42dc8c00bfa3bf8d4bcaeb4d89b9443b5f93c5045868f2f07069", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/list_set/mod.rs", - "byte_length": 7285, - "sha256": "65cddeae6751d636c34a445faf3d85537ccaa8836ebe7fa7a4f6278502492f22", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/manifest.rs", - "byte_length": 21199, - "sha256": "f8db83b093d791cacdd406712e1b9d2b914d88ee8295f43fa44b0ed06849d085", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/manifest/registry_v7.rs", - "byte_length": 38620, - "sha256": "93c3150f2f74264340264f68b2df328ed86fa7b5f39529bc8abac428137cfe16", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/message/decode.rs", - "byte_length": 1935, - "sha256": "30d0010373463c20c7e8687047a05e30a875712a7762b4ac26bcbab7af913083", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/message/encode.rs", - "byte_length": 1146, - "sha256": "704c24edd8b9480e2f83d5ce6eec231314c92cbdc809c137ad57e10831f4e4bb", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/message/mod.rs", - "byte_length": 78, - "sha256": "da18b75c116d8885b5f2f5218723d45af00dc8fed82d71564e1e664e8023fe70", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/message/tags.rs", - "byte_length": 11847, - "sha256": "831ce6c747c30919386511e8397f0f2c9d145f03ad05a7e7a2a01e74ca4f0db6", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/message_file/decode.rs", - "byte_length": 5654, - "sha256": "5cb3ea388d206cfcc7ff90a23f6f24d4767f8c2ce8f5f5276421a8b4ea74ec37", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/message_file/encode.rs", - "byte_length": 3580, - "sha256": "330d6efefd553c6774a47d5e28b74c33ca940876b21d6cd80c22901f0f83dab9", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/message_file/mod.rs", - "byte_length": 57, - "sha256": "5c57058e78fa54008ba63a97ba12aee00db28753fa39bdff3cb7347e3447dabc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/operational_listing/decode.rs", - "byte_length": 33577, - "sha256": "91acebfd9760417a9a11f7f8b46e7412c379792f7cbf96f343483b485db29415", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/operational_listing/encode.rs", - "byte_length": 5383, - "sha256": "d6287fe1167b4fd6b7119a32d2b9f1daa77fd4217f339d23d7a20d996c654c68", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/operational_listing/mod.rs", - "byte_length": 46, - "sha256": "0cbdae9059f46d9158b7b4ecc6814718fee407b6702903d16deff0da48970766", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/operational_listing/tags.rs", - "byte_length": 51990, - "sha256": "3a5d625fd652da1039fa7d16a9699ceae98ba1940bf1e081bdbc53f77f50026d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/order/decode.rs", - "byte_length": 44114, - "sha256": "40a30b1e15b06e93ede14082b0a048070624bc0707fab689081903d462262978", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/order/encode.rs", - "byte_length": 14566, - "sha256": "13a6b946188d418fdb4dbc67fda31c413ff176efd2375e1112c3efdd31528e41", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/order/mod.rs", - "byte_length": 677, - "sha256": "1afecc3a177c814dc2b02499ec4e6480aaa7814a8cab350e102db50023c3565a", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/order/tags.rs", - "byte_length": 23506, - "sha256": "ee115a43f984222777926255c491cbad438e1662b094906c974658bffa1237bc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/parsed.rs", - "byte_length": 5486, - "sha256": "a91ad5954c12662be047db90fb9baaf9772690f8515a55235b4e3fa191f9aea7", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/plot/decode.rs", - "byte_length": 4572, - "sha256": "5a307aeb02f7201079cfe632d8d2a1087ca6f7754849cd1693a572a79a69e84d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/plot/encode.rs", - "byte_length": 3476, - "sha256": "1c4ab8b964f1de524c0666fee14ae818be298ee69b657d460f39fbff3c5e707d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/plot/mod.rs", - "byte_length": 8421, - "sha256": "d18b8cbed3f921f436bcc63dcac2d830a05ac66c75c29440a8195b5cbff323b8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/post/admission.rs", - "byte_length": 5428, - "sha256": "e64ce9b93280ab259ee3738df2c880d3c50b4682f1bf903fc19c86f5f0555d32", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/post/authored.rs", - "byte_length": 2104, - "sha256": "85f1c9c0af9f1f46de17f0a9fc74c915d4ad948b39df32787f16b36d4abe19b8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/post/decode.rs", - "byte_length": 8987, - "sha256": "83a0e3a237012a04a932e6d9c205906ea72ff35669928e03bb7107facfe63c17", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/post/inbound.rs", - "byte_length": 86, - "sha256": "9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/post/inbound/registry_v7.rs", - "byte_length": 16016, - "sha256": "bc0bb839c28f3588f05544c846c987528ea3934c75305fef70af8e027a97cc58", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/post/inbound/registry_v7/tests.rs", - "byte_length": 9031, - "sha256": "77d6c75e25551df1ebf33a6a6d240038d52270186aa81dde9618f31c87c32cf9", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/post/mod.rs", - "byte_length": 70, - "sha256": "12e3b8d15be0efa6074ee506e0562f9dbdc2fe761122bbaf95c2624939015b7e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/profile/admission.rs", - "byte_length": 4091, - "sha256": "a4d46f055b99c7d607e4393d031cb8f0975ab9a4e105f6b63bfb56ee48f2475f", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/profile/authored.rs", - "byte_length": 6279, - "sha256": "6be8823575f718e421935eef5b6822e6ee0232876ce669c1bf57b13392b51001", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/profile/decode.rs", - "byte_length": 4065, - "sha256": "09bacfa0b677291eeb1e945997a80deaa9b06951168f029c2c5c297c01efd2c2", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/profile/inbound.rs", - "byte_length": 86, - "sha256": "9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/profile/inbound/registry_v7.rs", - "byte_length": 10394, - "sha256": "c37c225ea8153beb098a39c4a56203aa03382af12646f40cd3f8628663700702", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/profile/inbound/registry_v7/tests.rs", - "byte_length": 279, - "sha256": "6191b1bdc576ed91be1998deab3d20fe55a49adfd149d2f80ca9038174153d7a", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/profile/mod.rs", - "byte_length": 521, - "sha256": "5d1a3b92f34dcd5a4ce340be97fcdd5244687203940ac6056353b25f7c5f4122", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/reaction/decode.rs", - "byte_length": 4825, - "sha256": "f719e16e317bdb7c73bc0950575a29408f09610ad2cf7b5c30451b3229e7c8e7", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/reaction/encode.rs", - "byte_length": 4294, - "sha256": "31a5c8f090753e5b512f3b8931665ed86d5716c236c9d09881b108178037403c", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/reaction/mod.rs", - "byte_length": 32, - "sha256": "f72b49a09f84d980791360980c31534c1058bd90ba181ba7e97d871663998c0d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/relay_auth/decode.rs", - "byte_length": 1799, - "sha256": "36114252d1096b839c60851ea8b678a39ef931a1c18c811528f343293e1b3387", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/relay_auth/encode.rs", - "byte_length": 1288, - "sha256": "add7ae05d38c2c495be2c0dcddaf5f9ebef125cf3c7bbc5f6c9229311644cf2e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/relay_auth/mod.rs", - "byte_length": 5736, - "sha256": "71a0834e9e312dc69319578a1aa69d5f79a10e3dcb219b37dea57a0839510304", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/relay_document/decode.rs", - "byte_length": 309, - "sha256": "90302e941a2ae5d65965e6dc0ce1ef1e416fc4257a45c067da94cccebb9909aa", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/relay_document/encode.rs", - "byte_length": 338, - "sha256": "884eeceb1bd605c970b5bc17ef662ec5a2c463c2dbdc3102f6888e768924f2cb", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/relay_document/mod.rs", - "byte_length": 32, - "sha256": "f72b49a09f84d980791360980c31534c1058bd90ba181ba7e97d871663998c0d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/reply/admission.rs", - "byte_length": 3842, - "sha256": "b2723a29d5f10389d4f746dc9d242a96bca26071b34c41429af32e68fa2c62a9", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/reply/authored.rs", - "byte_length": 2521, - "sha256": "4141356b803e554d54a9667bd3f70c62fc8d6f743768a7a103b177bb7847ea88", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/reply/inbound.rs", - "byte_length": 86, - "sha256": "9994cbca9dcc7450ad133b6b927e7c06bee08ef813652452cc1e56a284c1be10", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/reply/inbound/registry_v7.rs", - "byte_length": 26837, - "sha256": "5b929d040f751520e15d52ccf44d7622a3eee44f136d958411515be67742bab3", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/reply/inbound/registry_v7/tests.rs", - "byte_length": 13030, - "sha256": "80a98b1f70193e2e4d9cd25bd8c31848914f55b1e47fb1dc222ae86ccb67aa10", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/reply/mod.rs", - "byte_length": 54, - "sha256": "986f59844194fbaac78618c6131940691d49dc888702366b3520c901a08990e0", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/report/decode.rs", - "byte_length": 6021, - "sha256": "bb7ae904636855252584dca0e2016a5aaef96ba1dbd596ff4b9601835c66d68b", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/report/encode.rs", - "byte_length": 5288, - "sha256": "35df56a61ee96e7cddaf73d7a0ef236263502be01229f0546f90f93fa609ca73", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/report/mod.rs", - "byte_length": 32, - "sha256": "f72b49a09f84d980791360980c31534c1058bd90ba181ba7e97d871663998c0d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/repost/decode.rs", - "byte_length": 5627, - "sha256": "45635907279f3d70247a8dd3a753524da4d4680168f406740fd4a64efd48422e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/repost/encode.rs", - "byte_length": 5973, - "sha256": "7765baebbe0f34cd6a763f4a328d3f1d794870f1141c9910a130fe14b0ab39b3", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/repost/mod.rs", - "byte_length": 32, - "sha256": "f72b49a09f84d980791360980c31534c1058bd90ba181ba7e97d871663998c0d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/resource_area/decode.rs", - "byte_length": 2573, - "sha256": "59e68071790bfe9775355225336287b04eabd926d6c0b391e5f9711fd05e1cbc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/resource_area/encode.rs", - "byte_length": 3099, - "sha256": "fa92ecbd5d5bf740c336842e02a66984e821931fe9e1fb890b43b966429227d7", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/resource_area/list_sets.rs", - "byte_length": 10445, - "sha256": "f3b9f9aefe3436b854098a53ea3aa13c5e08cf01ea56a2bca1c4a1b207e30e29", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/resource_area/mod.rs", - "byte_length": 8608, - "sha256": "73e2e5196e2eed084be2cbc0e3c34dc25d6621c74af15bc5673087f665e0dd18", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/resource_cap/decode.rs", - "byte_length": 2583, - "sha256": "883b7df150311ad3e500500e93398bb3bb337934bb1a6e95fbd5e3af554336bf", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/resource_cap/encode.rs", - "byte_length": 5463, - "sha256": "1e039a919f379cc90ade5c220904f140213b8161bba9234355705160a0b5f2c0", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/resource_cap/mod.rs", - "byte_length": 3535, - "sha256": "971797348274938b6ddf9674e85d9b680de76e5fdea74689c16df5da49d4c10d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/seal/decode.rs", - "byte_length": 1716, - "sha256": "eb0efa8796c0e32bb0709f3f29f54c9464d1aa3552dc3c49cc4571fedffb316d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/seal/encode.rs", - "byte_length": 1029, - "sha256": "1789c7e442002270874b20e8c1b1edde66eae9cf5e4e9ac85a1ffb5a79b199dc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/seal/mod.rs", - "byte_length": 57, - "sha256": "5c57058e78fa54008ba63a97ba12aee00db28753fa39bdff3cb7347e3447dabc", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/social_helpers.rs", - "byte_length": 11112, - "sha256": "94e2b757165c393c75a8005c71227019ab6fe72084aae4293a4c1fae7c424403", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/tag_builders.rs", - "byte_length": 9603, - "sha256": "f51b0bcb52735223c077949d29b7b5d7f8e97dbf12f8ea0a7fa1cd36420f46ee", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/test_fixtures.rs", - "byte_length": 4697, - "sha256": "f6e82c0cf780bcd12e559e3900c7eebd27287f21c9804b73ed180e4b45286146", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/trade/mod.rs", - "byte_length": 22362, - "sha256": "59605e4dac95bd211a15e1bbecb2a5c23adcb4fe71f1535d07b27aceca5a0604", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/verification.rs", - "byte_length": 253, - "sha256": "b49a32df605035c87f295c0a151140d43d2e588c5e11b05183e5fc92993dae0e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/verification/v1.rs", - "byte_length": 8739, - "sha256": "79eff26d9ea7207367c6667b2484c992a3a400a75b654012b4708e9f716b7e7e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/verification/v1/tests.rs", - "byte_length": 3634, - "sha256": "721cdee3c2b3c9fe5c74542460d4a44a6f02e9339391588ca2cc8573292a5cb4", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/wire.rs", - "byte_length": 644, - "sha256": "8debb7b142d11e841a2bd03434002d03206914ce7d45247145dfaf0ac72dfbe8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/wire/publication.rs", - "byte_length": 59629, - "sha256": "bd8130f4e1b0d3e20a03014449001c72eecc4fb97051c8877287e6bbbb309319", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/event_codec/src/wire/publication/allowlist.rs", - "byte_length": 21115, - "sha256": "d3bb5522a68b3f68d369c380403fec33c247e689288515446a8b8f5bd6b1ba5e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "publication_allowlist_vector_executor", - "path": "crates/event_codec/tests/publication_allowlist.rs", - "byte_length": 7562, - "sha256": "342d3d3d1100cb5d31aca94b0540b1e03761b034de23475bdb5142baabeed8fd", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "publication_vector_executor", - "path": "crates/event_codec/tests/publication_artifact.rs", - "byte_length": 34582, - "sha256": "7a31169eac4217a38cb3ef25eb9213f2f89e11fb17e76ceaf7449b34225e98af", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "event_store_manifest_authority", - "path": "crates/event_store/Cargo.toml", - "byte_length": 1529, - "sha256": "4bddb3462a7543c9a7981ead5cf1027988fc381457432f4b04b0e9c43f6d51ca", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "transport_manifest_authority", - "path": "crates/transport/Cargo.toml", - "byte_length": 706, - "sha256": "98856ba5a00bf7183a1f2d0120cccb50ab68cbbc3091ba15d4c8f7d0052d364b", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/transport/src/delivery.rs", - "byte_length": 20085, - "sha256": "d80bba7e20c3c80ebffef23314220038f16f508c64ba4836e3d04280e199f6ff", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/transport/src/error.rs", - "byte_length": 5352, - "sha256": "a26488936a6d844622975d9fd8a000f7cd3d4d4967f8689bd0fe9458cb6c27a9", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/transport/src/kind.rs", - "byte_length": 2943, - "sha256": "9cc8a4b7c95442fd9d2d902a2d47f98c8752355bcb17f7c4b9cdbf50f71b54b7", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/transport/src/lib.rs", - "byte_length": 1916, - "sha256": "e9b9bd50dc5ee19f92ab75b5f277c10e9b330d36b9d68fbb7db57d27ddb76621", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/transport/src/message.rs", - "byte_length": 297, - "sha256": "722a0b50aa90508ebdacbc87751dc296c38aa925920a9aa9ce8e88d0f6d73fd8", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/transport/src/payload.rs", - "byte_length": 9895, - "sha256": "f2fd757050471b2055b2b1cf71f311bd2c729e009ee802996a33a92d67eaad2d", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/transport/src/reticulum.rs", - "byte_length": 8193, - "sha256": "4c2846f0081cd2f7560805a9b8f79b0af4417a575b7d6d6e44aa86975a124d61", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/transport/src/status.rs", - "byte_length": 11330, - "sha256": "cd99b96c1fd370d0e88c1cbb159e952e16a747a72d5cc967b71784123dc1947f", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/transport/src/target.rs", - "byte_length": 23937, - "sha256": "fa788cba1f5bac28bd0084ebaf5f0daea5d037f5de6ef74836fd46a6054ea3d0", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "public_production_source", - "path": "crates/transport/src/transport.rs", - "byte_length": 2060, - "sha256": "4d2f639f7214bc06c8cc7f7483513a706221f8e5f2f3f03f79f24892e8acb729", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "rust_toolchain_authority", - "path": "rust-toolchain.toml", - "byte_length": 132, - "sha256": "c33aa38292bab6513bf79ed2f69c1525b736dd738b15ca78af713b70b29265c9", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "xtask_manifest_authority", - "path": "tools/xtask/Cargo.toml", - "byte_length": 1173, - "sha256": "b915e0289bf7390d3c4194aaed1e748cf5e591426e0443c310775ddc7d7f63a5", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "contract_command_authority", - "path": "tools/xtask/src/contract.rs", - "byte_length": 482590, - "sha256": "715d1908bf1e0fb61a62a3420442243f2c70990d8bfb79896706659dc6be7387", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "superseded_nip09_contract_governance", - "path": "tools/xtask/src/contract/nip09_reconciliation.rs", - "byte_length": 854772, - "sha256": "35f0ec2e60ff64942a30d8ae0632cb039a5ecac68b2a50f4fc6c452f9bb4738c", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "publication_allowlist_contract_governance", - "path": "tools/xtask/src/contract/phase1_publication_allowlist.rs", - "byte_length": 77019, - "sha256": "72d8b350eaf3e18b2bf3e03e6de3adbd855bd70894d0f0a540d446151c674016", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "publication_contract_governance", - "path": "tools/xtask/src/contract/phase1_publication_artifact.rs", - "byte_length": 75960, - "sha256": "ac1a146c72edaec6e5bec92933cfaf13c71d506b89930b295c773536da21a985", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "superseded_raw_rebuild_contract_governance", - "path": "tools/xtask/src/contract/raw_source_rebuild.rs", - "byte_length": 302449, - "sha256": "91179375ebae13daca8e6d22286fd68cc849903f1397909b37ad0e639a961c4e", - "hash_algorithm": "sha256_bytes_v1" - }, - { - "role": "contract_dispatch_authority", - "path": "tools/xtask/src/main.rs", - "byte_length": 17100, - "sha256": "cce879d7d351fd1d359a0368cf84edb31412e3d17d730b3ddc19f56ff23d3bea", - "hash_algorithm": "sha256_bytes_v1" - } - ], - "result_vector": { - "canonical_path": "contracts/conformance/vectors/publication/phase1_allowlist.v1.json", - "mirror_path": "crates/event_codec/tests/fixtures/phase1_publication_allowlist.v1.json", - "byte_length": 49975, - "sha256": "2867ee401db8cfad3a77869847c57567e869623f55d3d6c9e98a7fa0a643c3d6", - "hash_algorithm": "sha256_bytes_v1", - "executor_path": "crates/event_codec/tests/publication_allowlist.rs", "executor_test": "publication_allowlist_conformance_vector_executes_every_case", "valid_case_ids": [ "sealed_profile_is_allowed", @@ -2319,11 +283,5 @@ "group_event_is_rejected", "operations_event_is_rejected" ] - }, - "release": { - "record_path": "contracts/releases/1.0.0-alpha.1.toml", - "change_id": "phase1-publication-allowlist", - "changelog_path": "CHANGELOG.md", - "changelog_marker": "<!-- release-change: phase1-publication-allowlist -->" } } diff --git a/crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.schema.json b/crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.schema.json @@ -61,38 +61,6 @@ "event_contract_id" ], "type": "object" - }, - "source": { - "additionalProperties": false, - "properties": { - "byte_length": { - "minimum": 1, - "type": "integer" - }, - "hash_algorithm": { - "const": "sha256_bytes_v1" - }, - "path": { - "minLength": 1, - "type": "string" - }, - "role": { - "minLength": 1, - "type": "string" - }, - "sha256": { - "pattern": "^[0-9a-f]{64}$", - "type": "string" - } - }, - "required": [ - "role", - "path", - "byte_length", - "sha256", - "hash_algorithm" - ], - "type": "object" } }, "$id": "https://radroots.org/contracts/event-codec/phase1-publication-allowlist-v1.schema.json", @@ -146,6 +114,16 @@ "input_type": { "const": "RadrootsPhase1PublicationArtifact" }, + "invariants": { + "const": [ + "sealed_artifact_revalidation_v1", + "closed_seven_leaf_inventory_v1", + "kind1_ask_then_photo_update_then_update_precedence_v1", + "strict_typed_nip52_date_or_time_v1", + "classified_listing_partition_before_food_validation_v1", + "excluded_product_families_fail_closed_v1" + ] + }, "kind_one_precedence": { "const": [ "ask", @@ -174,6 +152,7 @@ "kind_one_precedence", "event_policy", "classified_listing_policy", + "invariants", "denied_families", "granted_capability", "excluded_capabilities" @@ -243,29 +222,48 @@ "minItems": 16, "type": "array" }, - "release": { - "additionalProperties": false, - "properties": { - "change_id": { - "const": "phase1-publication-allowlist" - }, - "changelog_marker": { - "const": "<!-- release-change: phase1-publication-allowlist -->" - }, - "changelog_path": { - "const": "CHANGELOG.md" - }, - "record_path": { - "const": "contracts/releases/1.0.0-alpha.1.toml" + "protocol_sources": { + "const": [ + { + "id": "nostr_nips", + "repository": "https://github.com/nostr-protocol/nips", + "revision": "bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91" + }, + { + "id": "blossom", + "repository": "https://github.com/hzrd149/blossom", + "revision": "b5bd2801d1763aa635fc8fea7a76597e0eb18990" } - }, - "required": [ - "record_path", - "change_id", - "changelog_path", - "changelog_marker" - ], - "type": "object" + ] + }, + "public_api": { + "const": { + "constants": [ + "RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_VERSION", + "RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_CONTRACT_ID", + "RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_REGISTRY_VERSION" + ], + "functions": [ + "allow_phase1_publication_artifact", + "allow_phase1_publication_canonical_json" + ], + "methods": [ + "RadrootsPhase1AllowlistedPublicationArtifact::artifact", + "RadrootsPhase1AllowlistedPublicationArtifact::into_artifact", + "RadrootsPhase1AllowlistedPublicationArtifact::leaf", + "RadrootsPhase1PublicationAllowlistError::code", + "RadrootsPhase1PublicationAllowlistError::source_code", + "RadrootsPhase1PublicationLeaf::as_str", + "RadrootsPhase1PublicationLeaf::authored_operation_id", + "RadrootsPhase1PublicationLeaf::event_contract_id", + "RadrootsPhase1PublicationLeaf::kind" + ], + "types": [ + "RadrootsPhase1PublicationLeaf", + "RadrootsPhase1AllowlistedPublicationArtifact", + "RadrootsPhase1PublicationAllowlistError" + ] + } }, "result_vector": { "additionalProperties": false, @@ -277,8 +275,8 @@ "canonical_path": { "const": "contracts/conformance/vectors/publication/phase1_allowlist.v1.json" }, - "executor_path": { - "const": "crates/event_codec/tests/publication_allowlist.rs" + "executor": { + "$ref": "#/$defs/file" }, "executor_test": { "const": "publication_allowlist_conformance_vector_executes_every_case" @@ -316,7 +314,7 @@ "byte_length", "sha256", "hash_algorithm", - "executor_path", + "executor", "executor_test", "valid_case_ids", "invalid_case_ids" @@ -325,13 +323,6 @@ }, "schema_version": { "const": 1 - }, - "source_files": { - "items": { - "$ref": "#/$defs/source" - }, - "minItems": 1, - "type": "array" } }, "required": [ @@ -341,11 +332,11 @@ "manifest_schema", "predecessor", "event_contract_registry", + "protocol_sources", + "public_api", "allowlist", "predecessor_source_supersessions", - "source_files", - "result_vector", - "release" + "result_vector" ], "title": "Radroots Phase 1 Publication Allowlist Contract", "type": "object" diff --git a/crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.sha256 b/crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.sha256 @@ -1 +1 @@ -4b82348c001a25e855ab945e7d3191cd85925614b270e90fdbf85450a4a3dd6d +8629b5c547e8f9daad473ab9d570b206d00db134cc22b4d8e81be10d0f3d10ec diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs @@ -17,6 +17,7 @@ mod phase1_publication_artifact; #[allow(dead_code)] mod raw_source_rebuild; mod registry_v7; +mod release_provenance; mod source_maintenance; pub(crate) use food_availability_projection::{ @@ -35,6 +36,9 @@ pub(crate) use phase1_publication_artifact::{ pub(crate) use registry_v7::{ validate_event_contract_registry_v7_inventory, write_event_contract_registry_v7_inventory, }; +pub(crate) use release_provenance::{ + validate_release_provenance_schema, write_release_provenance, write_release_provenance_schema, +}; pub(crate) use source_maintenance::{ validate_source_maintenance_manifest, write_source_maintenance_manifest, }; @@ -68,6 +72,7 @@ pub(crate) fn validate_artifact_contracts(workspace_root: &Path) -> Result<(), S validate_raw_source_rebuild_manifest(workspace_root)?; validate_immutable_phase1_publication_artifact_predecessor(workspace_root)?; validate_phase1_publication_allowlist_manifest(workspace_root)?; + validate_release_provenance_schema(workspace_root)?; blossom_publication_readiness::validate_blossom_publication_readiness(workspace_root)?; validate_knowledge_contract_manifest(workspace_root) } diff --git a/tools/xtask/src/contract/phase1_publication_allowlist.rs b/tools/xtask/src/contract/phase1_publication_allowlist.rs @@ -1,7 +1,7 @@ use super::{ artifact_bundle::{GeneratedArtifact, read_regular_file, with_artifact_bundle_transaction}, phase1_publication_artifact::{ - PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS, source_specs, + PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS, validate_immutable_phase1_publication_artifact_predecessor_under_lock, }, }; @@ -14,6 +14,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; use sha2::{Digest, Sha256}; use std::{collections::BTreeSet, path::Path}; +use syn::{ImplItem, Item, Visibility}; const SCHEMA_VERSION: u32 = 1; const CONTRACT_ID: &str = "radroots_event_codec.phase1_publication_allowlist_v1"; @@ -37,10 +38,7 @@ const VECTOR_MIRROR_RELATIVE: &str = const VECTOR_EXECUTOR_RELATIVE: &str = "crates/event_codec/tests/publication_allowlist.rs"; const VECTOR_EXECUTOR_TEST: &str = "publication_allowlist_conformance_vector_executes_every_case"; const OPERATIONS_RELATIVE: &str = "contracts/operations.toml"; -const RELEASE_RELATIVE: &str = "contracts/releases/1.0.0-alpha.1.toml"; -const CHANGELOG_RELATIVE: &str = "CHANGELOG.md"; -const RELEASE_CHANGE_ID: &str = "phase1-publication-allowlist"; -const CHANGELOG_MARKER: &str = "<!-- release-change: phase1-publication-allowlist -->"; +const ALLOWLIST_SOURCE_RELATIVE: &str = "crates/event_codec/src/wire/publication/allowlist.rs"; const REGISTRY_RELATIVE: &str = "contracts/event_store/event_contract_registry_v7.inventory.json"; const REGISTRY_SIDECAR_RELATIVE: &str = "contracts/event_store/event_contract_registry_v7.inventory.sha256"; @@ -98,19 +96,51 @@ const PREDECESSOR_ARTIFACTS: &[ImmutableArtifactSpec] = &[ ), ]; -const GENERATED_ARTIFACT_PATHS: &[&str] = &[ - MANIFEST_RELATIVE, - MANIFEST_SCHEMA_RELATIVE, - MANIFEST_SHA256_RELATIVE, - GENERATED_DESCRIPTOR_RELATIVE, - VECTOR_MIRROR_RELATIVE, -]; - const PUBLIC_TYPES: &[&str] = &[ "RadrootsPhase1PublicationLeaf", "RadrootsPhase1AllowlistedPublicationArtifact", "RadrootsPhase1PublicationAllowlistError", ]; +const PUBLIC_CONSTANTS: &[&str] = &[ + "RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_VERSION", + "RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_CONTRACT_ID", + "RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_REGISTRY_VERSION", +]; +const PUBLIC_FUNCTIONS: &[&str] = &[ + "allow_phase1_publication_artifact", + "allow_phase1_publication_canonical_json", +]; +const PUBLIC_METHODS: &[&str] = &[ + "RadrootsPhase1AllowlistedPublicationArtifact::artifact", + "RadrootsPhase1AllowlistedPublicationArtifact::into_artifact", + "RadrootsPhase1AllowlistedPublicationArtifact::leaf", + "RadrootsPhase1PublicationAllowlistError::code", + "RadrootsPhase1PublicationAllowlistError::source_code", + "RadrootsPhase1PublicationLeaf::as_str", + "RadrootsPhase1PublicationLeaf::authored_operation_id", + "RadrootsPhase1PublicationLeaf::event_contract_id", + "RadrootsPhase1PublicationLeaf::kind", +]; +const PROTOCOL_SOURCE_PINS: &[(&str, &str, &str)] = &[ + ( + "nostr_nips", + "https://github.com/nostr-protocol/nips", + "bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91", + ), + ( + "blossom", + "https://github.com/hzrd149/blossom", + "b5bd2801d1763aa635fc8fea7a76597e0eb18990", + ), +]; +const SEMANTIC_INVARIANTS: &[&str] = &[ + "sealed_artifact_revalidation_v1", + "closed_seven_leaf_inventory_v1", + "kind1_ask_then_photo_update_then_update_precedence_v1", + "strict_typed_nip52_date_or_time_v1", + "classified_listing_partition_before_food_validation_v1", + "excluded_product_families_fail_closed_v1", +]; const LEAVES: &[LeafSpec] = &[ LeafSpec::new( @@ -528,16 +558,6 @@ struct FileDescriptor { #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -struct SourceFileDescriptor { - role: String, - path: String, - byte_length: u64, - sha256: String, - hash_algorithm: String, -} - -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] struct PredecessorDescriptor { contract_id: String, immutable_artifacts: Vec<FileDescriptor>, @@ -554,6 +574,23 @@ struct RegistryDescriptor { #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(deny_unknown_fields)] +struct ProtocolSourcePin { + id: String, + repository: String, + revision: String, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct PublicApiDescriptor { + constants: Vec<String>, + types: Vec<String>, + functions: Vec<String>, + methods: Vec<String>, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] struct LeafDescriptor { leaf: String, kind: u32, @@ -575,6 +612,7 @@ struct AllowlistDescriptor { kind_one_precedence: Vec<String>, event_policy: String, classified_listing_policy: String, + invariants: Vec<String>, denied_families: Vec<String>, granted_capability: String, excluded_capabilities: Vec<String>, @@ -588,7 +626,7 @@ struct ResultVectorDescriptor { byte_length: u64, sha256: String, hash_algorithm: String, - executor_path: String, + executor: FileDescriptor, executor_test: String, valid_case_ids: Vec<String>, invalid_case_ids: Vec<String>, @@ -596,15 +634,6 @@ struct ResultVectorDescriptor { #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(deny_unknown_fields)] -struct ReleaseDescriptor { - record_path: String, - change_id: String, - changelog_path: String, - changelog_marker: String, -} - -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] struct AllowlistManifest { schema_version: u32, contract_id: String, @@ -612,11 +641,11 @@ struct AllowlistManifest { manifest_schema: FileDescriptor, predecessor: PredecessorDescriptor, event_contract_registry: RegistryDescriptor, + protocol_sources: Vec<ProtocolSourcePin>, + public_api: PublicApiDescriptor, allowlist: AllowlistDescriptor, predecessor_source_supersessions: Vec<String>, - source_files: Vec<SourceFileDescriptor>, result_vector: ResultVectorDescriptor, - release: ReleaseDescriptor, } #[derive(Debug, Deserialize)] @@ -739,10 +768,6 @@ fn describe_manifest( workspace_root: &Path, schema_bytes: &[u8], ) -> Result<AllowlistManifest, String> { - let source_files = successor_source_specs(workspace_root)? - .into_iter() - .map(|(role, path)| source_descriptor(workspace_root, &role, &path)) - .collect::<Result<Vec<_>, _>>()?; let vector = validate_vector(workspace_root)?; Ok(AllowlistManifest { schema_version: SCHEMA_VERSION, @@ -768,32 +793,47 @@ fn describe_manifest( evolution: "immutable_registry_v7_plus_additive_phase1_publication_allowlist_v1" .to_owned(), }, + protocol_sources: expected_protocol_sources(), + public_api: expected_public_api_descriptor(), allowlist: expected_allowlist_descriptor(), predecessor_source_supersessions: PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS .iter() .map(|path| (*path).to_owned()) .collect(), - source_files, result_vector: ResultVectorDescriptor { canonical_path: VECTOR_RELATIVE.to_owned(), mirror_path: VECTOR_MIRROR_RELATIVE.to_owned(), byte_length: vector.bytes.len() as u64, sha256: sha256_hex(&vector.bytes), hash_algorithm: HASH_ALGORITHM.to_owned(), - executor_path: VECTOR_EXECUTOR_RELATIVE.to_owned(), + executor: descriptor_for_file(workspace_root, VECTOR_EXECUTOR_RELATIVE)?, executor_test: VECTOR_EXECUTOR_TEST.to_owned(), valid_case_ids: vector.valid_case_ids, invalid_case_ids: vector.invalid_case_ids, }, - release: ReleaseDescriptor { - record_path: RELEASE_RELATIVE.to_owned(), - change_id: RELEASE_CHANGE_ID.to_owned(), - changelog_path: CHANGELOG_RELATIVE.to_owned(), - changelog_marker: CHANGELOG_MARKER.to_owned(), - }, }) } +fn expected_protocol_sources() -> Vec<ProtocolSourcePin> { + PROTOCOL_SOURCE_PINS + .iter() + .map(|(id, repository, revision)| ProtocolSourcePin { + id: (*id).to_owned(), + repository: (*repository).to_owned(), + revision: (*revision).to_owned(), + }) + .collect() +} + +fn expected_public_api_descriptor() -> PublicApiDescriptor { + PublicApiDescriptor { + constants: owned(PUBLIC_CONSTANTS), + types: owned(PUBLIC_TYPES), + functions: owned(PUBLIC_FUNCTIONS), + methods: owned(PUBLIC_METHODS), + } +} + fn expected_allowlist_descriptor() -> AllowlistDescriptor { AllowlistDescriptor { version: RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_VERSION, @@ -819,6 +859,7 @@ fn expected_allowlist_descriptor() -> AllowlistDescriptor { event_policy: "strict_typed_nip52_date_or_time_artifact_only_v1".to_owned(), classified_listing_policy: "raw_marker_partition_before_focused_food_profile_validation_v1" .to_owned(), + invariants: owned(SEMANTIC_INVARIANTS), denied_families: DENIED_FAMILIES .iter() .map(|family| (*family).to_owned()) @@ -839,33 +880,95 @@ fn expected_allowlist_descriptor() -> AllowlistDescriptor { } } +fn owned(values: &[&str]) -> Vec<String> { + values.iter().map(|value| (*value).to_owned()).collect() +} + fn validate_source_contract(workspace_root: &Path) -> Result<(), String> { validate_registry_identity(workspace_root)?; validate_operations_authority(workspace_root)?; - validate_release_authority(workspace_root)?; + validate_public_api_authority(workspace_root)?; validate_vector(workspace_root)?; - let paths = successor_source_specs(workspace_root)? - .into_iter() - .map(|(_, path)| path) + let superseded = PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS + .iter() + .copied() .collect::<BTreeSet<_>>(); - for path in PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS { - if !paths.contains(*path) { - return Err(format!( - "allowlist successor does not bind superseded predecessor source {path}" - )); + if superseded.len() != PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS.len() { + return Err("allowlist predecessor supersession paths must be unique".to_owned()); + } + Ok(()) +} + +fn validate_public_api_authority(workspace_root: &Path) -> Result<(), String> { + let bytes = read_regular_file(workspace_root, ALLOWLIST_SOURCE_RELATIVE)?; + let source = std::str::from_utf8(&bytes) + .map_err(|error| format!("{ALLOWLIST_SOURCE_RELATIVE} must be UTF-8: {error}"))?; + validate_public_api_source(source) +} + +fn validate_public_api_source(source: &str) -> Result<(), String> { + let file = syn::parse_file(source) + .map_err(|error| format!("parse {ALLOWLIST_SOURCE_RELATIVE}: {error}"))?; + let mut constants = BTreeSet::new(); + let mut types = BTreeSet::new(); + let mut functions = BTreeSet::new(); + let mut methods = BTreeSet::new(); + for item in &file.items { + match item { + Item::Const(item) if is_public(&item.vis) => { + constants.insert(item.ident.to_string()); + } + Item::Enum(item) if is_public(&item.vis) => { + types.insert(item.ident.to_string()); + } + Item::Struct(item) if is_public(&item.vis) => { + types.insert(item.ident.to_string()); + } + Item::Fn(item) if is_public(&item.vis) => { + functions.insert(item.sig.ident.to_string()); + } + Item::Impl(item) if item.trait_.is_none() => { + let syn::Type::Path(self_type) = item.self_ty.as_ref() else { + continue; + }; + let Some(type_name) = self_type.path.segments.last() else { + continue; + }; + for method in &item.items { + if let ImplItem::Fn(method) = method + && is_public(&method.vis) + { + methods.insert(format!("{}::{}", type_name.ident, method.sig.ident)); + } + } + } + _ => {} } } - for path in GENERATED_ARTIFACT_PATHS { - if paths.contains(*path) { + for (label, actual, expected) in [ + ("constants", constants, expected_set(PUBLIC_CONSTANTS)), + ("types", types, expected_set(PUBLIC_TYPES)), + ("functions", functions, expected_set(PUBLIC_FUNCTIONS)), + ("methods", methods, expected_set(PUBLIC_METHODS)), + ] { + if actual != expected { return Err(format!( - "allowlist source inventory recursively includes generated artifact {path}" + "{ALLOWLIST_SOURCE_RELATIVE} public {label} drifted: expected {expected:?}, found {actual:?}" )); } } Ok(()) } +fn is_public(visibility: &Visibility) -> bool { + matches!(visibility, Visibility::Public(_)) +} + +fn expected_set(values: &[&str]) -> BTreeSet<String> { + values.iter().map(|value| (*value).to_owned()).collect() +} + fn validate_registry_identity(workspace_root: &Path) -> Result<(), String> { for (path, expected_len, expected_sha) in [ (REGISTRY_RELATIVE, REGISTRY_BYTE_LENGTH, REGISTRY_SHA256), @@ -1020,47 +1123,6 @@ fn validate_allowlist_operation( Ok(()) } -fn validate_release_authority(workspace_root: &Path) -> Result<(), String> { - let release = parse_toml(workspace_root, RELEASE_RELATIVE)?; - let changes = release - .get("changes") - .and_then(toml::Value::as_array) - .ok_or_else(|| format!("{RELEASE_RELATIVE} has no changes"))?; - let matches = changes - .iter() - .filter(|change| change.get("id").and_then(toml::Value::as_str) == Some(RELEASE_CHANGE_ID)) - .collect::<Vec<_>>(); - if matches.len() != 1 - || matches[0] - .get("classification") - .and_then(toml::Value::as_str) - != Some("feature") - { - return Err(format!( - "{RELEASE_RELATIVE} must contain one feature change {RELEASE_CHANGE_ID}" - )); - } - let impacts = toml_string_array("allowlist semver impacts", matches[0].get("semver_impacts"))?; - for required in [ - "add_exported_type", - "add_exported_function", - "add_exported_constant", - "add_conformance_vector", - ] { - if !impacts.iter().any(|impact| impact == required) { - return Err(format!("allowlist release change is missing {required}")); - } - } - let changelog = String::from_utf8(read_regular_file(workspace_root, CHANGELOG_RELATIVE)?) - .map_err(|error| format!("{CHANGELOG_RELATIVE} must be UTF-8: {error}"))?; - if changelog.matches(CHANGELOG_MARKER).count() != 1 { - return Err(format!( - "{CHANGELOG_RELATIVE} must contain {CHANGELOG_MARKER} exactly once" - )); - } - Ok(()) -} - fn validate_vector(workspace_root: &Path) -> Result<ValidatedVector, String> { let bytes = read_regular_file(workspace_root, VECTOR_RELATIVE)?; let suite: VectorSuite = serde_json::from_slice(&bytes) @@ -1614,35 +1676,13 @@ fn forbid_tag_name(case_id: &str, tags: &[Value], forbidden: &str) -> Result<(), } } -fn successor_source_specs(workspace_root: &Path) -> Result<Vec<(String, String)>, String> { - let mut specs = source_specs(workspace_root)?; - specs.extend([ - ( - "publication_allowlist_vector_executor".to_owned(), - VECTOR_EXECUTOR_RELATIVE.to_owned(), - ), - ( - "publication_allowlist_contract_governance".to_owned(), - "tools/xtask/src/contract/phase1_publication_allowlist.rs".to_owned(), - ), - ]); - specs.sort_by(|left, right| left.1.cmp(&right.1)); - let mut seen = BTreeSet::new(); - for (_, path) in &specs { - if !seen.insert(path.as_str()) { - return Err(format!( - "publication allowlist source inventory duplicates {path}" - )); - } - } - Ok(specs) -} - fn validate_manifest_shape(manifest: &AllowlistManifest) -> Result<(), String> { if manifest.schema_version != SCHEMA_VERSION || manifest.contract_id != CONTRACT_ID || manifest.authority_id != AUTHORITY_ID || manifest.predecessor.contract_id != PREDECESSOR_CONTRACT_ID + || manifest.protocol_sources != expected_protocol_sources() + || manifest.public_api != expected_public_api_descriptor() || manifest.allowlist != expected_allowlist_descriptor() || manifest.predecessor_source_supersessions != PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS @@ -1651,7 +1691,8 @@ fn validate_manifest_shape(manifest: &AllowlistManifest) -> Result<(), String> { .collect::<Vec<_>>() || manifest.result_vector.canonical_path != VECTOR_RELATIVE || manifest.result_vector.mirror_path != VECTOR_MIRROR_RELATIVE - || manifest.release.change_id != RELEASE_CHANGE_ID + || manifest.result_vector.executor.path != VECTOR_EXECUTOR_RELATIVE + || manifest.result_vector.executor_test != VECTOR_EXECUTOR_TEST { return Err(format!("{MANIFEST_RELATIVE} shape drifted")); } @@ -1683,11 +1724,10 @@ fn validate_manifest_shape(manifest: &AllowlistManifest) -> Result<(), String> { "{MANIFEST_RELATIVE} predecessor or registry identity drifted" )); } - let mut paths = BTreeSet::new(); - for source in &manifest.source_files { - if source.hash_algorithm != HASH_ALGORITHM || !paths.insert(source.path.as_str()) { - return Err(format!("{MANIFEST_RELATIVE} source inventory is invalid")); - } + if manifest.result_vector.executor.hash_algorithm != HASH_ALGORITHM { + return Err(format!( + "{MANIFEST_RELATIVE} behavior executor hash algorithm drifted" + )); } Ok(()) } @@ -1701,8 +1741,8 @@ fn manifest_schema() -> Value { "additionalProperties": false, "required": [ "schema_version", "contract_id", "authority_id", "manifest_schema", "predecessor", - "event_contract_registry", "allowlist", "predecessor_source_supersessions", - "source_files", "result_vector", "release" + "event_contract_registry", "protocol_sources", "public_api", "allowlist", + "predecessor_source_supersessions", "result_vector" ], "properties": { "schema_version": {"const": SCHEMA_VERSION}, @@ -1727,9 +1767,11 @@ fn manifest_schema() -> Value { "evolution": {"const": "immutable_registry_v7_plus_additive_phase1_publication_allowlist_v1"} } }, + "protocol_sources": {"const": expected_protocol_sources()}, + "public_api": {"const": expected_public_api_descriptor()}, "allowlist": { "type": "object", "additionalProperties": false, - "required": ["version", "contract_id", "operation_id", "canonical_json_operation_id", "input_type", "output_type", "allowed_leaves", "kind_one_precedence", "event_policy", "classified_listing_policy", "denied_families", "granted_capability", "excluded_capabilities"], + "required": ["version", "contract_id", "operation_id", "canonical_json_operation_id", "input_type", "output_type", "allowed_leaves", "kind_one_precedence", "event_policy", "classified_listing_policy", "invariants", "denied_families", "granted_capability", "excluded_capabilities"], "properties": { "version": {"const": 1}, "contract_id": {"const": RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_CONTRACT_ID}, @@ -1741,6 +1783,7 @@ fn manifest_schema() -> Value { "kind_one_precedence": {"const": ["ask", "photo_update", "update"]}, "event_policy": {"const": "strict_typed_nip52_date_or_time_artifact_only_v1"}, "classified_listing_policy": {"const": "raw_marker_partition_before_focused_food_profile_validation_v1"}, + "invariants": {"const": owned(SEMANTIC_INVARIANTS)}, "denied_families": {"type": "array", "minItems": 19, "maxItems": 19, "items": {"type": "string", "minLength": 1}}, "granted_capability": {"const": "phase1_durable_publication_lane_entry_only_v1"}, "excluded_capabilities": {"type": "array", "minItems": 7, "maxItems": 7, "items": {"type": "string", "minLength": 1}} @@ -1752,31 +1795,20 @@ fn manifest_schema() -> Value { "maxItems": PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS.len(), "items": {"type": "string", "minLength": 1} }, - "source_files": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/source"}}, "result_vector": { "type": "object", "additionalProperties": false, - "required": ["canonical_path", "mirror_path", "byte_length", "sha256", "hash_algorithm", "executor_path", "executor_test", "valid_case_ids", "invalid_case_ids"], + "required": ["canonical_path", "mirror_path", "byte_length", "sha256", "hash_algorithm", "executor", "executor_test", "valid_case_ids", "invalid_case_ids"], "properties": { "canonical_path": {"const": VECTOR_RELATIVE}, "mirror_path": {"const": VECTOR_MIRROR_RELATIVE}, "byte_length": {"type": "integer", "minimum": 1}, "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, "hash_algorithm": {"const": HASH_ALGORITHM}, - "executor_path": {"const": VECTOR_EXECUTOR_RELATIVE}, + "executor": {"$ref": "#/$defs/file"}, "executor_test": {"const": VECTOR_EXECUTOR_TEST}, "valid_case_ids": {"type": "array", "minItems": 14, "maxItems": 14, "items": {"type": "string"}}, "invalid_case_ids": {"type": "array", "minItems": 25, "maxItems": 25, "items": {"type": "string"}} } - }, - "release": { - "type": "object", "additionalProperties": false, - "required": ["record_path", "change_id", "changelog_path", "changelog_marker"], - "properties": { - "record_path": {"const": RELEASE_RELATIVE}, - "change_id": {"const": RELEASE_CHANGE_ID}, - "changelog_path": {"const": CHANGELOG_RELATIVE}, - "changelog_marker": {"const": CHANGELOG_MARKER} - } } }, "$defs": { @@ -1790,17 +1822,6 @@ fn manifest_schema() -> Value { "hash_algorithm": {"const": HASH_ALGORITHM} } }, - "source": { - "type": "object", "additionalProperties": false, - "required": ["role", "path", "byte_length", "sha256", "hash_algorithm"], - "properties": { - "role": {"type": "string", "minLength": 1}, - "path": {"type": "string", "minLength": 1}, - "byte_length": {"type": "integer", "minimum": 1}, - "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, - "hash_algorithm": {"const": HASH_ALGORITHM} - } - }, "leaf": { "type": "object", "additionalProperties": false, "required": ["leaf", "kind", "publication_role", "authored_operation_id", "event_contract_id"], @@ -1816,21 +1837,6 @@ fn manifest_schema() -> Value { }) } -fn source_descriptor( - workspace_root: &Path, - role: &str, - path: &str, -) -> Result<SourceFileDescriptor, String> { - let bytes = read_regular_file(workspace_root, path)?; - Ok(SourceFileDescriptor { - role: role.to_owned(), - path: path.to_owned(), - byte_length: bytes.len() as u64, - sha256: sha256_hex(&bytes), - hash_algorithm: HASH_ALGORITHM.to_owned(), - }) -} - fn descriptor_for_file(workspace_root: &Path, path: &str) -> Result<FileDescriptor, String> { descriptor_for_bytes(path, &read_regular_file(workspace_root, path)?) } @@ -1907,6 +1913,7 @@ fn sha256_hex(bytes: &[u8]) -> String { #[cfg(test)] mod tests { use super::*; + use std::fs; use std::path::PathBuf; fn workspace_root() -> PathBuf { @@ -1918,19 +1925,21 @@ mod tests { } #[test] - fn allowlist_source_inventory_is_closed_and_unique() { - let specs = successor_source_specs(&workspace_root()).expect("allowlist sources"); - let paths = specs - .iter() - .map(|(_, path)| path.as_str()) - .collect::<BTreeSet<_>>(); - assert_eq!(paths.len(), specs.len()); - assert!(paths.contains("crates/event_codec/src/wire/publication/allowlist.rs")); - assert!(paths.contains(VECTOR_EXECUTOR_RELATIVE)); - assert!(paths.contains("tools/xtask/src/contract/phase1_publication_allowlist.rs")); - for generated in GENERATED_ARTIFACT_PATHS { - assert!(!paths.contains(generated)); - } + fn allowlist_public_api_is_validated_from_the_rust_ast() { + let root = workspace_root(); + let source = fs::read_to_string(root.join(ALLOWLIST_SOURCE_RELATIVE)).unwrap(); + validate_public_api_source(&source).expect("current public API AST"); + validate_public_api_source(&format!("// unrelated comment\n{source}")) + .expect("comments do not change public API identity"); + + let private_function = source.replacen( + "pub fn allow_phase1_publication_artifact(", + "fn allow_phase1_publication_artifact(", + 1, + ); + let error = validate_public_api_source(&private_function) + .expect_err("removing an operation from the public API must fail"); + assert!(error.contains("public functions drifted"), "{error}"); } #[test] @@ -1938,11 +1947,98 @@ mod tests { let root = workspace_root(); validate_registry_identity(&root).expect("registry identity"); validate_operations_authority(&root).expect("operation authority"); - validate_release_authority(&root).expect("release authority"); + validate_public_api_authority(&root).expect("public API authority"); validate_vector(&root).expect("allowlist vector"); } #[test] + fn semantic_contract_ignores_unrelated_source_and_release_bytes() { + let root = workspace_root(); + let temp = tempfile::tempdir().expect("semantic authority workspace"); + copy_semantic_authorities(&root, temp.path()); + let schema_bytes = canonical_json_bytes(&manifest_schema()).expect("schema bytes"); + let before = canonical_json_bytes( + &describe_manifest(temp.path(), &schema_bytes).expect("baseline semantic manifest"), + ) + .expect("baseline bytes"); + + write_temp_file(temp.path(), "Cargo.lock", b"unrelated lockfile mutation\n"); + write_temp_file( + temp.path(), + "CHANGELOG.md", + b"unrelated release note mutation\n", + ); + write_temp_file( + temp.path(), + "crates/event_codec/src/unrelated.rs", + b"pub fn unrelated() {}\n", + ); + let after = canonical_json_bytes( + &describe_manifest(temp.path(), &schema_bytes).expect("stable semantic manifest"), + ) + .expect("stable bytes"); + assert_eq!(before, after); + + let value: Value = serde_json::from_slice(&after).expect("semantic manifest JSON"); + assert!(value.get("source_files").is_none()); + assert!(value.get("release").is_none()); + } + + #[test] + fn semantic_contract_changes_for_schema_and_behavior_executor_bytes() { + let root = workspace_root(); + let temp = tempfile::tempdir().expect("semantic authority workspace"); + copy_semantic_authorities(&root, temp.path()); + let schema = manifest_schema(); + let schema_bytes = canonical_json_bytes(&schema).expect("schema bytes"); + let baseline = canonical_json_bytes( + &describe_manifest(temp.path(), &schema_bytes).expect("baseline semantic manifest"), + ) + .expect("baseline bytes"); + + let mut changed_schema = schema; + changed_schema["title"] = Value::String("Changed governed schema".to_owned()); + let changed_schema_bytes = canonical_json_bytes(&changed_schema).expect("changed schema"); + let changed_schema_manifest = canonical_json_bytes( + &describe_manifest(temp.path(), &changed_schema_bytes) + .expect("schema-sensitive semantic manifest"), + ) + .expect("changed schema manifest"); + assert_ne!(baseline, changed_schema_manifest); + + let executor = temp.path().join(VECTOR_EXECUTOR_RELATIVE); + let mut bytes = fs::read(&executor).expect("behavior executor"); + bytes.extend_from_slice(b"\n// governed behavior executor mutation\n"); + fs::write(&executor, bytes).expect("mutate behavior executor"); + let changed_executor_manifest = canonical_json_bytes( + &describe_manifest(temp.path(), &schema_bytes) + .expect("executor-sensitive semantic manifest"), + ) + .expect("changed executor manifest"); + assert_ne!(baseline, changed_executor_manifest); + } + + #[test] + fn semantic_contract_rejects_governed_vector_mutation() { + let root = workspace_root(); + let temp = tempfile::tempdir().expect("semantic authority workspace"); + copy_semantic_authorities(&root, temp.path()); + let vector_path = temp.path().join(VECTOR_RELATIVE); + let mut vector: Value = + serde_json::from_slice(&fs::read(&vector_path).expect("vector bytes")).unwrap(); + vector["vectors"][0]["id"] = Value::String("governed_vector_mutation".to_owned()); + fs::write( + &vector_path, + canonical_json_bytes(&vector).expect("mutated vector bytes"), + ) + .expect("mutate governed vector"); + let schema_bytes = canonical_json_bytes(&manifest_schema()).expect("schema bytes"); + let error = describe_manifest(temp.path(), &schema_bytes) + .expect_err("governed vector mutation must fail closed"); + assert!(error.contains("case inventory or order drifted"), "{error}"); + } + + #[test] fn allowlist_vector_semantic_witnesses_fail_closed() { let bytes = read_regular_file(&workspace_root(), VECTOR_RELATIVE).expect("vector bytes"); let suite: VectorSuite = serde_json::from_slice(&bytes).expect("vector suite"); @@ -1988,4 +2084,23 @@ mod tests { value["unexpected"] = Value::Bool(true); validate_json_schema(&schema, &value).expect_err("unknown field must fail"); } + + fn copy_semantic_authorities(source_root: &Path, destination_root: &Path) { + for relative in [ + REGISTRY_RELATIVE, + REGISTRY_SIDECAR_RELATIVE, + VECTOR_RELATIVE, + VECTOR_EXECUTOR_RELATIVE, + ] { + let bytes = fs::read(source_root.join(relative)).expect("semantic authority source"); + write_temp_file(destination_root, relative, &bytes); + } + } + + fn write_temp_file(root: &Path, relative: &str, bytes: &[u8]) { + let path = root.join(relative); + fs::create_dir_all(path.parent().expect("temporary file parent")) + .expect("create temporary parent"); + fs::write(path, bytes).expect("write temporary semantic authority"); + } } diff --git a/tools/xtask/src/contract/release_provenance.rs b/tools/xtask/src/contract/release_provenance.rs @@ -0,0 +1,1483 @@ +use super::artifact_bundle::{ + GeneratedArtifact, read_regular_file, with_artifact_bundle_transaction, +}; +use serde::{Deserialize, Serialize}; +use serde_json::{Map, Value, json}; +use sha2::{Digest, Sha256}; +use std::collections::{BTreeMap, BTreeSet}; +use std::fs; +use std::io::Write; +use std::path::{Component, Path, PathBuf}; +use std::process::Command; + +const SCHEMA_VERSION: u32 = 1; +const CONTRACT_ID: &str = "radroots.release.phase1_publication_provenance.v1"; +const AUTHORITY: &str = "candidate_release_evidence_not_protocol_authority_v1"; +const HASH_ALGORITHM: &str = "sha256_bytes_v1"; +const SCHEMA_RELATIVE: &str = + "contracts/releases/provenance/phase1_publication_release_provenance_v1.schema.json"; +const SEMANTIC_CONTRACT_RELATIVE: &str = + "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.json"; +const TOOLCHAIN_RELATIVE: &str = "rust-toolchain.toml"; +const LOCKFILE_RELATIVE: &str = "Cargo.lock"; +const FEATURE_PROFILES_RELATIVE: &str = "contracts/coverage-profiles.toml"; +const PUBLISH_POLICY_RELATIVE: &str = "contracts/releases/publish_policy.toml"; +const WRITE_SCHEMA_COMMAND: &str = "cargo xtask contract release-provenance-schema --write"; +const METADATA_COMMAND: &str = "cargo metadata --locked --format-version 1"; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct FileDescriptor { + path: String, + byte_length: u64, + sha256: String, + hash_algorithm: String, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct GitCandidate { + commit_oid: String, + tree_oid: String, + object_format: String, + clean_worktree: bool, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct ToolchainEvidence { + channel: String, + components: Vec<String>, + targets: Vec<String>, + contract: FileDescriptor, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct DependencyGraphEvidence { + command: String, + normalization: String, + package_count: u64, + node_count: u64, + normalized_byte_length: u64, + normalized_sha256: String, + hash_algorithm: String, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct FeatureProfile { + package: String, + no_default_features: bool, + features: Vec<String>, + test_threads: u32, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct FeatureProfileEvidence { + contract: FileDescriptor, + selected: Vec<FeatureProfile>, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct PackageArchive { + package: String, + version: String, + filename: String, + byte_length: u64, + sha256: String, + hash_algorithm: String, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct PackageEvidence { + release_version: String, + publish_policy: FileDescriptor, + archives: Vec<PackageArchive>, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct CommandEvidence { + purpose: String, + command: String, + required_result: String, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct ReleaseProvenanceManifest { + schema_version: u32, + contract_id: String, + authority: String, + manifest_schema: FileDescriptor, + semantic_contract: FileDescriptor, + candidate: GitCandidate, + source_digest: GitTreeDigest, + toolchain: ToolchainEvidence, + lockfile: FileDescriptor, + dependency_graph: DependencyGraphEvidence, + feature_profiles: FeatureProfileEvidence, + packages: PackageEvidence, + commands: Vec<CommandEvidence>, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(deny_unknown_fields)] +struct GitTreeDigest { + algorithm: String, + oid: String, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct RustToolchainFile { + toolchain: RustToolchain, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct RustToolchain { + channel: String, + #[serde(default)] + components: Vec<String>, + #[serde(default)] + targets: Vec<String>, +} + +#[derive(Clone, Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct TestProfile { + no_default_features: bool, + features: Vec<String>, + test_threads: u32, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct TestProfilesFile { + profiles: TestProfiles, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct TestProfiles { + default: TestProfile, + #[serde(default)] + crates: BTreeMap<String, TestProfile>, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct PublishPolicy { + release: ReleaseVersion, + classification: ReleaseClassification, + publish_order: PublishOrder, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct ReleaseVersion { + version: String, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct ReleaseClassification { + public: Vec<String>, + internal: Vec<String>, + deferred: Vec<String>, + retired: Vec<String>, + yank_only: Vec<String>, +} + +#[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] +struct PublishOrder { + crates: Vec<String>, +} + +pub(crate) fn write_release_provenance_schema(workspace_root: &Path) -> Result<(), String> { + with_artifact_bundle_transaction(workspace_root, |transaction| { + transaction.write(vec![GeneratedArtifact { + relative: SCHEMA_RELATIVE, + contents: canonical_json_bytes(&release_provenance_schema())?, + }])?; + validate_release_provenance_schema_under_lock(workspace_root) + }) +} + +pub(crate) fn validate_release_provenance_schema(workspace_root: &Path) -> Result<(), String> { + with_artifact_bundle_transaction(workspace_root, |_| { + validate_release_provenance_schema_under_lock(workspace_root) + }) +} + +fn validate_release_provenance_schema_under_lock(workspace_root: &Path) -> Result<(), String> { + let expected = canonical_json_bytes(&release_provenance_schema())?; + let actual = read_regular_file(workspace_root, SCHEMA_RELATIVE)?; + if actual != expected { + return Err(format!( + "generated release provenance schema {SCHEMA_RELATIVE} is stale; run {WRITE_SCHEMA_COMMAND}" + )); + } + let schema: Value = serde_json::from_slice(&actual) + .map_err(|error| format!("parse {SCHEMA_RELATIVE}: {error}"))?; + jsonschema::validator_for(&schema) + .map_err(|error| format!("compile {SCHEMA_RELATIVE}: {error}"))?; + Ok(()) +} + +pub(crate) fn write_release_provenance( + workspace_root: &Path, + package_directory: &Path, + output: &Path, +) -> Result<(), String> { + validate_release_provenance_schema(workspace_root)?; + require_output_outside_workspace(workspace_root, output)?; + let manifest = describe_release_provenance(workspace_root, package_directory)?; + validate_manifest(&manifest)?; + write_external_atomic(output, &canonical_json_bytes(&manifest)?) +} + +fn describe_release_provenance( + workspace_root: &Path, + package_directory: &Path, +) -> Result<ReleaseProvenanceManifest, String> { + ensure_clean_git_worktree(workspace_root)?; + let commit_oid = git_stdout(workspace_root, &["rev-parse", "HEAD"])?; + let tree_oid = git_stdout(workspace_root, &["rev-parse", "HEAD^{tree}"])?; + let git_object_format = object_format(&commit_oid)?; + if object_format(&tree_oid)? != git_object_format { + return Err("Git commit and tree object formats differ".to_owned()); + } + + let schema = descriptor_for_file(workspace_root, SCHEMA_RELATIVE)?; + let semantic_contract = descriptor_for_file(workspace_root, SEMANTIC_CONTRACT_RELATIVE)?; + let lockfile = descriptor_for_file(workspace_root, LOCKFILE_RELATIVE)?; + let toolchain = load_toolchain(workspace_root)?; + let policy = load_toml::<PublishPolicy>(workspace_root, PUBLISH_POLICY_RELATIVE)?; + validate_publish_policy(&policy)?; + let feature_profiles = selected_feature_profiles(workspace_root, &policy)?; + let dependency_graph = dependency_graph_evidence(workspace_root)?; + let packages = package_evidence(workspace_root, package_directory, &policy)?; + + Ok(ReleaseProvenanceManifest { + schema_version: SCHEMA_VERSION, + contract_id: CONTRACT_ID.to_owned(), + authority: AUTHORITY.to_owned(), + manifest_schema: schema, + semantic_contract, + candidate: GitCandidate { + commit_oid: commit_oid.clone(), + tree_oid: tree_oid.clone(), + object_format: git_object_format.to_owned(), + clean_worktree: true, + }, + source_digest: GitTreeDigest { + algorithm: format!("git_tree_{git_object_format}_v1"), + oid: tree_oid, + }, + toolchain, + lockfile, + dependency_graph, + feature_profiles, + packages, + commands: required_commands(), + }) +} + +fn validate_manifest(manifest: &ReleaseProvenanceManifest) -> Result<(), String> { + let commit_format = object_format(&manifest.candidate.commit_oid)?; + let tree_format = object_format(&manifest.candidate.tree_oid)?; + if manifest.schema_version != SCHEMA_VERSION + || manifest.contract_id != CONTRACT_ID + || manifest.authority != AUTHORITY + || !manifest.candidate.clean_worktree + || commit_format != manifest.candidate.object_format + || tree_format != manifest.candidate.object_format + || manifest.source_digest.oid != manifest.candidate.tree_oid + || manifest.source_digest.algorithm + != format!("git_tree_{}_v1", manifest.candidate.object_format) + || manifest.manifest_schema.path != SCHEMA_RELATIVE + || manifest.semantic_contract.path != SEMANTIC_CONTRACT_RELATIVE + || manifest.toolchain.contract.path != TOOLCHAIN_RELATIVE + || manifest.lockfile.path != LOCKFILE_RELATIVE + || manifest.feature_profiles.contract.path != FEATURE_PROFILES_RELATIVE + || manifest.packages.publish_policy.path != PUBLISH_POLICY_RELATIVE + || manifest.commands != required_commands() + { + return Err("release provenance manifest authority or Git identity drifted".to_owned()); + } + let profiles = manifest + .feature_profiles + .selected + .iter() + .map(|profile| profile.package.as_str()) + .collect::<Vec<_>>(); + let archives = manifest + .packages + .archives + .iter() + .map(|archive| archive.package.as_str()) + .collect::<Vec<_>>(); + if profiles != archives + || manifest.packages.archives.iter().any(|archive| { + archive.version != manifest.packages.release_version + || archive.filename != format!("{}-{}.crate", archive.package, archive.version) + }) + { + return Err( + "release provenance feature profiles and package archives must cover the same ordered public package set" + .to_owned(), + ); + } + let schema = release_provenance_schema(); + let instance = serde_json::to_value(manifest) + .map_err(|error| format!("serialize release provenance manifest: {error}"))?; + validate_json_schema(&schema, &instance) +} + +fn load_toolchain(workspace_root: &Path) -> Result<ToolchainEvidence, String> { + let toolchain = load_toml::<RustToolchainFile>(workspace_root, TOOLCHAIN_RELATIVE)?.toolchain; + require_unique_nonempty(&toolchain.components, "toolchain.components")?; + require_unique_nonempty(&toolchain.targets, "toolchain.targets")?; + if toolchain.channel.trim().is_empty() { + return Err("toolchain.channel must not be empty".to_owned()); + } + Ok(ToolchainEvidence { + channel: toolchain.channel, + components: toolchain.components, + targets: toolchain.targets, + contract: descriptor_for_file(workspace_root, TOOLCHAIN_RELATIVE)?, + }) +} + +fn selected_feature_profiles( + workspace_root: &Path, + policy: &PublishPolicy, +) -> Result<FeatureProfileEvidence, String> { + let profiles = + load_toml::<TestProfilesFile>(workspace_root, FEATURE_PROFILES_RELATIVE)?.profiles; + validate_test_profile("profiles.default", &profiles.default)?; + for (package, profile) in &profiles.crates { + validate_test_profile(&format!("profiles.crates.{package}"), profile)?; + } + let classified = all_classified_packages(policy); + let public = policy + .classification + .public + .iter() + .cloned() + .collect::<BTreeSet<_>>(); + let unknown_overrides = profiles + .crates + .keys() + .filter(|package| !classified.contains(*package)) + .cloned() + .collect::<Vec<_>>(); + if !unknown_overrides.is_empty() { + return Err(format!( + "feature profile overrides reference unknown packages: {}", + unknown_overrides.join(", ") + )); + } + let selected = policy + .publish_order + .crates + .iter() + .filter(|package| public.contains(*package)) + .map(|package| { + let profile = profiles.crates.get(package).unwrap_or(&profiles.default); + FeatureProfile { + package: package.clone(), + no_default_features: profile.no_default_features, + features: profile.features.clone(), + test_threads: profile.test_threads, + } + }) + .collect(); + Ok(FeatureProfileEvidence { + contract: descriptor_for_file(workspace_root, FEATURE_PROFILES_RELATIVE)?, + selected, + }) +} + +fn validate_test_profile(label: &str, profile: &TestProfile) -> Result<(), String> { + if profile.test_threads == 0 { + return Err(format!("{label}.test_threads must be positive")); + } + require_unique_nonempty(&profile.features, &format!("{label}.features")) +} + +fn validate_publish_policy(policy: &PublishPolicy) -> Result<(), String> { + if policy.release.version.trim().is_empty() { + return Err("release.version must not be empty".to_owned()); + } + for (label, packages) in [ + ("classification.public", &policy.classification.public), + ("classification.internal", &policy.classification.internal), + ("classification.deferred", &policy.classification.deferred), + ("classification.retired", &policy.classification.retired), + ("classification.yank_only", &policy.classification.yank_only), + ] { + require_unique_nonempty(packages, label)?; + } + let all = all_classified_packages(policy); + let classified_count = policy.classification.public.len() + + policy.classification.internal.len() + + policy.classification.deferred.len() + + policy.classification.retired.len() + + policy.classification.yank_only.len(); + if all.len() != classified_count { + return Err("release package classifications must be pairwise unique".to_owned()); + } + require_unique_nonempty(&policy.publish_order.crates, "publish_order.crates")?; + let public = policy + .classification + .public + .iter() + .cloned() + .collect::<BTreeSet<_>>(); + let ordered = policy + .publish_order + .crates + .iter() + .cloned() + .collect::<BTreeSet<_>>(); + if ordered != public { + return Err( + "publish_order.crates must contain every public package exactly once".to_owned(), + ); + } + Ok(()) +} + +fn all_classified_packages(policy: &PublishPolicy) -> BTreeSet<String> { + policy + .classification + .public + .iter() + .chain(&policy.classification.internal) + .chain(&policy.classification.deferred) + .chain(&policy.classification.retired) + .chain(&policy.classification.yank_only) + .cloned() + .collect() +} + +fn package_evidence( + workspace_root: &Path, + package_directory: &Path, + policy: &PublishPolicy, +) -> Result<PackageEvidence, String> { + let archives = describe_package_archives(package_directory, policy)?; + Ok(PackageEvidence { + release_version: policy.release.version.clone(), + publish_policy: descriptor_for_file(workspace_root, PUBLISH_POLICY_RELATIVE)?, + archives, + }) +} + +fn describe_package_archives( + package_directory: &Path, + policy: &PublishPolicy, +) -> Result<Vec<PackageArchive>, String> { + let expected = policy + .publish_order + .crates + .iter() + .map(|package| { + ( + format!("{package}-{}.crate", policy.release.version), + package, + ) + }) + .collect::<BTreeMap<_, _>>(); + let mut found = BTreeMap::new(); + let entries = fs::read_dir(package_directory).map_err(|error| { + format!( + "read package archive directory {}: {error}", + package_directory.display() + ) + })?; + for entry in entries { + let entry = entry.map_err(|error| { + format!( + "read package archive entry in {}: {error}", + package_directory.display() + ) + })?; + let file_type = entry + .file_type() + .map_err(|error| format!("inspect {}: {error}", entry.path().display()))?; + let name = entry + .file_name() + .into_string() + .map_err(|_| "package archive filename must be UTF-8".to_owned())?; + if !name.ends_with(".crate") { + continue; + } + if !file_type.is_file() || file_type.is_symlink() { + return Err(format!("package archive {name} must be a regular file")); + } + let package = expected + .get(&name) + .ok_or_else(|| format!("unexpected package archive {name}"))?; + let bytes = fs::read(entry.path()) + .map_err(|error| format!("read package archive {name}: {error}"))?; + if bytes.is_empty() { + return Err(format!("package archive {name} must not be empty")); + } + if found + .insert( + (*package).clone(), + PackageArchive { + package: (*package).clone(), + version: policy.release.version.clone(), + filename: name, + byte_length: bytes.len() as u64, + sha256: sha256_hex(&bytes), + hash_algorithm: HASH_ALGORITHM.to_owned(), + }, + ) + .is_some() + { + return Err(format!("duplicate package archive for {package}")); + } + } + let missing = policy + .publish_order + .crates + .iter() + .filter(|package| !found.contains_key(*package)) + .cloned() + .collect::<Vec<_>>(); + if !missing.is_empty() { + return Err(format!("missing package archives: {}", missing.join(", "))); + } + policy + .publish_order + .crates + .iter() + .map(|package| { + found + .remove(package) + .ok_or_else(|| format!("missing package archive for {package}")) + }) + .collect() +} + +fn dependency_graph_evidence(workspace_root: &Path) -> Result<DependencyGraphEvidence, String> { + let output = Command::new("cargo") + .args(["metadata", "--locked", "--format-version", "1"]) + .current_dir(workspace_root) + .output() + .map_err(|error| format!("run {METADATA_COMMAND}: {error}"))?; + if !output.status.success() { + return Err(format!( + "{METADATA_COMMAND} failed: {}", + String::from_utf8_lossy(&output.stderr).trim() + )); + } + let metadata: Value = serde_json::from_slice(&output.stdout) + .map_err(|error| format!("parse cargo metadata: {error}"))?; + validate_metadata_source_roots(workspace_root, &metadata)?; + let normalized = normalize_metadata(&metadata)?; + let bytes = canonical_json_bytes(&normalized)?; + let package_count = normalized["packages"] + .as_array() + .map_or(0, |packages| packages.len()) as u64; + let node_count = normalized["resolve"]["nodes"] + .as_array() + .map_or(0, |nodes| nodes.len()) as u64; + Ok(DependencyGraphEvidence { + command: METADATA_COMMAND.to_owned(), + normalization: "cargo_metadata_semantic_graph_v1".to_owned(), + package_count, + node_count, + normalized_byte_length: bytes.len() as u64, + normalized_sha256: sha256_hex(&bytes), + hash_algorithm: HASH_ALGORITHM.to_owned(), + }) +} + +fn validate_metadata_source_roots(workspace_root: &Path, metadata: &Value) -> Result<(), String> { + let workspace = fs::canonicalize(workspace_root) + .map_err(|error| format!("canonicalize {}: {error}", workspace_root.display()))?; + for package in required_array(metadata, "packages")? { + if package + .get("source") + .is_some_and(|source| !source.is_null()) + { + continue; + } + let manifest_path = required_string(package, "manifest_path")?; + let canonical = fs::canonicalize(manifest_path) + .map_err(|error| format!("canonicalize package manifest {manifest_path}: {error}"))?; + if !canonical.starts_with(&workspace) { + return Err(format!( + "unversioned path package {} is outside the candidate source tree: {manifest_path}", + required_string(package, "name")? + )); + } + } + Ok(()) +} + +fn normalize_metadata(metadata: &Value) -> Result<Value, String> { + let packages = required_array(metadata, "packages")?; + let mut stable_ids = BTreeMap::new(); + let mut unique_stable_ids = BTreeSet::new(); + for package in packages { + let id = required_string(package, "id")?; + let name = required_string(package, "name")?; + let version = required_string(package, "version")?; + let source = package + .get("source") + .and_then(Value::as_str) + .unwrap_or("workspace"); + let stable = format!("{name}@{version}|{source}"); + if !unique_stable_ids.insert(stable.clone()) { + return Err(format!( + "cargo metadata package identities collide after path-independent normalization: {stable}" + )); + } + if stable_ids.insert(id.to_owned(), stable).is_some() { + return Err(format!("duplicate cargo metadata package id {id}")); + } + } + + let mut normalized_packages = packages + .iter() + .map(|package| normalize_package(package, &stable_ids)) + .collect::<Result<Vec<_>, _>>()?; + sort_values(&mut normalized_packages)?; + + let resolve = metadata + .get("resolve") + .and_then(Value::as_object) + .ok_or_else(|| "cargo metadata resolve must be an object".to_owned())?; + let mut nodes = required_array(&Value::Object(resolve.clone()), "nodes")? + .iter() + .map(|node| normalize_node(node, &stable_ids)) + .collect::<Result<Vec<_>, _>>()?; + sort_values(&mut nodes)?; + + Ok(json!({ + "format_version": metadata.get("version").cloned().unwrap_or_else(|| json!(1)), + "packages": normalized_packages, + "resolve": { + "root": normalize_optional_id(resolve.get("root"), &stable_ids)?, + "nodes": nodes, + }, + "workspace_members": normalize_id_array(metadata.get("workspace_members"), &stable_ids)?, + "workspace_default_members": normalize_id_array(metadata.get("workspace_default_members"), &stable_ids)?, + })) +} + +fn normalize_package( + package: &Value, + stable_ids: &BTreeMap<String, String>, +) -> Result<Value, String> { + let original_id = required_string(package, "id")?; + let mut dependencies = required_array(package, "dependencies")? + .iter() + .map(normalize_dependency) + .collect::<Result<Vec<_>, _>>()?; + sort_values(&mut dependencies)?; + let mut targets = required_array(package, "targets")? + .iter() + .map(normalize_target) + .collect::<Result<Vec<_>, _>>()?; + sort_values(&mut targets)?; + let features = package + .get("features") + .and_then(Value::as_object) + .ok_or_else(|| "cargo metadata package features must be an object".to_owned())?; + let normalized_features = features + .iter() + .map(|(name, values)| { + Ok(( + name.clone(), + Value::Array(sorted_string_values(values, "package feature")?), + )) + }) + .collect::<Result<Map<_, _>, String>>()?; + Ok(json!({ + "id": stable_id(original_id, stable_ids)?, + "name": required_string(package, "name")?, + "version": required_string(package, "version")?, + "source": package.get("source").cloned().unwrap_or(Value::Null), + "checksum": package.get("checksum").cloned().unwrap_or(Value::Null), + "edition": package.get("edition").cloned().unwrap_or(Value::Null), + "rust_version": package.get("rust_version").cloned().unwrap_or(Value::Null), + "features": normalized_features, + "dependencies": dependencies, + "targets": targets, + })) +} + +fn normalize_dependency(dependency: &Value) -> Result<Value, String> { + Ok(json!({ + "name": required_string(dependency, "name")?, + "source": dependency.get("source").cloned().unwrap_or(Value::Null), + "req": dependency.get("req").cloned().unwrap_or(Value::Null), + "kind": dependency.get("kind").cloned().unwrap_or(Value::Null), + "rename": dependency.get("rename").cloned().unwrap_or(Value::Null), + "optional": dependency.get("optional").cloned().unwrap_or(Value::Bool(false)), + "uses_default_features": dependency.get("uses_default_features").cloned().unwrap_or(Value::Bool(true)), + "features": sorted_string_values(dependency.get("features").unwrap_or(&Value::Null), "dependency features")?, + "target": dependency.get("target").cloned().unwrap_or(Value::Null), + "registry": dependency.get("registry").cloned().unwrap_or(Value::Null), + })) +} + +fn normalize_target(target: &Value) -> Result<Value, String> { + Ok(json!({ + "name": required_string(target, "name")?, + "kind": sorted_string_values(target.get("kind").unwrap_or(&Value::Null), "target kind")?, + "crate_types": sorted_string_values(target.get("crate_types").unwrap_or(&Value::Null), "target crate_types")?, + "edition": target.get("edition").cloned().unwrap_or(Value::Null), + "doctest": target.get("doctest").cloned().unwrap_or(Value::Null), + "test": target.get("test").cloned().unwrap_or(Value::Null), + "doc": target.get("doc").cloned().unwrap_or(Value::Null), + })) +} + +fn normalize_node(node: &Value, stable_ids: &BTreeMap<String, String>) -> Result<Value, String> { + let mut dependencies = node + .get("deps") + .and_then(Value::as_array) + .ok_or_else(|| "cargo metadata resolve node deps must be an array".to_owned())? + .iter() + .map(|dependency| { + let mut dep_kinds = required_array(dependency, "dep_kinds")? + .iter() + .map(|kind| { + Ok(json!({ + "kind": kind.get("kind").cloned().unwrap_or(Value::Null), + "target": kind.get("target").cloned().unwrap_or(Value::Null), + })) + }) + .collect::<Result<Vec<_>, String>>()?; + sort_values(&mut dep_kinds)?; + Ok(json!({ + "name": required_string(dependency, "name")?, + "pkg": stable_id(required_string(dependency, "pkg")?, stable_ids)?, + "dep_kinds": dep_kinds, + })) + }) + .collect::<Result<Vec<_>, String>>()?; + sort_values(&mut dependencies)?; + Ok(json!({ + "id": stable_id(required_string(node, "id")?, stable_ids)?, + "dependencies": dependencies, + "features": sorted_string_values(node.get("features").unwrap_or(&Value::Null), "node features")?, + })) +} + +fn normalize_optional_id( + value: Option<&Value>, + stable_ids: &BTreeMap<String, String>, +) -> Result<Value, String> { + match value { + None | Some(Value::Null) => Ok(Value::Null), + Some(Value::String(id)) => Ok(Value::String(stable_id(id, stable_ids)?.to_owned())), + Some(_) => Err("cargo metadata resolve root must be a string or null".to_owned()), + } +} + +fn normalize_id_array( + value: Option<&Value>, + stable_ids: &BTreeMap<String, String>, +) -> Result<Vec<String>, String> { + let values = value + .and_then(Value::as_array) + .ok_or_else(|| "cargo metadata workspace member list must be an array".to_owned())?; + let mut normalized = values + .iter() + .map(|value| { + let id = value + .as_str() + .ok_or_else(|| "cargo metadata package id must be a string".to_owned())?; + stable_id(id, stable_ids).map(str::to_owned) + }) + .collect::<Result<Vec<_>, _>>()?; + normalized.sort(); + Ok(normalized) +} + +fn stable_id<'a>(id: &str, stable_ids: &'a BTreeMap<String, String>) -> Result<&'a str, String> { + stable_ids + .get(id) + .map(String::as_str) + .ok_or_else(|| format!("cargo metadata references unknown package id {id}")) +} + +fn required_array<'a>(value: &'a Value, key: &str) -> Result<&'a Vec<Value>, String> { + value + .get(key) + .and_then(Value::as_array) + .ok_or_else(|| format!("cargo metadata {key} must be an array")) +} + +fn required_string<'a>(value: &'a Value, key: &str) -> Result<&'a str, String> { + value + .get(key) + .and_then(Value::as_str) + .ok_or_else(|| format!("cargo metadata {key} must be a string")) +} + +fn sorted_string_values(value: &Value, label: &str) -> Result<Vec<Value>, String> { + let values = value + .as_array() + .ok_or_else(|| format!("{label} must be an array"))?; + let mut strings = values + .iter() + .map(|value| { + value + .as_str() + .map(str::to_owned) + .ok_or_else(|| format!("{label} entries must be strings")) + }) + .collect::<Result<Vec<_>, _>>()?; + strings.sort(); + Ok(strings.into_iter().map(Value::String).collect()) +} + +fn sort_values(values: &mut [Value]) -> Result<(), String> { + let mut keyed = values + .iter() + .map(|value| canonical_json_bytes(value).map(|key| (key, value))) + .collect::<Result<Vec<_>, _>>()?; + keyed.sort_by(|left, right| left.0.cmp(&right.0)); + let ordered = keyed + .into_iter() + .map(|(_, value)| value.clone()) + .collect::<Vec<_>>(); + values.clone_from_slice(&ordered); + Ok(()) +} + +fn required_commands() -> Vec<CommandEvidence> { + [ + ( + "dependency_graph", + METADATA_COMMAND, + "exit_zero_and_normalized_digest_match", + ), + ( + "package_archive_verification", + "cargo xtask release preflight", + "exit_zero_for_exact_clean_candidate", + ), + ( + "canonical_release_lane", + "nix run .#release-preflight", + "exit_zero_for_exact_clean_candidate", + ), + ( + "provenance_collection", + "cargo xtask release provenance --package-dir <PACKAGE_DIR> --out <OUTPUT>", + "canonical_manifest_written_outside_source_worktree", + ), + ] + .into_iter() + .map(|(purpose, command, required_result)| CommandEvidence { + purpose: purpose.to_owned(), + command: command.to_owned(), + required_result: required_result.to_owned(), + }) + .collect() +} + +fn ensure_clean_git_worktree(workspace_root: &Path) -> Result<(), String> { + let output = Command::new("git") + .args(["status", "--porcelain=v1", "--untracked-files=all"]) + .current_dir(workspace_root) + .output() + .map_err(|error| format!("inspect Git worktree: {error}"))?; + if !output.status.success() { + return Err(format!( + "inspect Git worktree failed: {}", + String::from_utf8_lossy(&output.stderr).trim() + )); + } + if !output.stdout.is_empty() { + return Err("release provenance requires an exact clean Git worktree".to_owned()); + } + Ok(()) +} + +fn git_stdout(workspace_root: &Path, args: &[&str]) -> Result<String, String> { + let output = Command::new("git") + .args(args) + .current_dir(workspace_root) + .output() + .map_err(|error| format!("run git {}: {error}", args.join(" ")))?; + if !output.status.success() { + return Err(format!( + "git {} failed: {}", + args.join(" "), + String::from_utf8_lossy(&output.stderr).trim() + )); + } + String::from_utf8(output.stdout) + .map(|value| value.trim().to_owned()) + .map_err(|error| format!("git {} output must be UTF-8: {error}", args.join(" "))) +} + +fn object_format(oid: &str) -> Result<&'static str, String> { + if !oid + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) + { + return Err("Git object id must be lowercase hexadecimal".to_owned()); + } + match oid.len() { + 40 => Ok("sha1"), + 64 => Ok("sha256"), + length => Err(format!("unsupported Git object id length {length}")), + } +} + +fn require_output_outside_workspace(workspace_root: &Path, output: &Path) -> Result<(), String> { + let workspace = fs::canonicalize(workspace_root) + .map_err(|error| format!("canonicalize {}: {error}", workspace_root.display()))?; + let absolute_output = absolute_lexical(output)?; + let resolved_output = resolve_existing_ancestor(&absolute_output)?; + if absolute_output.starts_with(&workspace) || resolved_output.starts_with(&workspace) { + return Err("release provenance output must be outside the source worktree".to_owned()); + } + Ok(()) +} + +fn resolve_existing_ancestor(path: &Path) -> Result<PathBuf, String> { + let mut ancestor = path; + let mut suffix = Vec::new(); + while !ancestor.exists() { + let name = ancestor + .file_name() + .ok_or_else(|| format!("output path has no existing ancestor: {}", path.display()))?; + suffix.push(name.to_owned()); + ancestor = ancestor + .parent() + .ok_or_else(|| format!("output path has no parent: {}", path.display()))?; + } + let mut resolved = fs::canonicalize(ancestor) + .map_err(|error| format!("canonicalize {}: {error}", ancestor.display()))?; + for component in suffix.into_iter().rev() { + resolved.push(component); + } + Ok(resolved) +} + +fn absolute_lexical(path: &Path) -> Result<PathBuf, String> { + let absolute = if path.is_absolute() { + path.to_path_buf() + } else { + std::env::current_dir() + .map_err(|error| format!("resolve current directory: {error}"))? + .join(path) + }; + let mut normalized = PathBuf::new(); + for component in absolute.components() { + match component { + Component::Prefix(_) | Component::RootDir | Component::Normal(_) => { + normalized.push(component.as_os_str()); + } + Component::CurDir => {} + Component::ParentDir => { + if !normalized.pop() { + return Err(format!("cannot normalize output path {}", path.display())); + } + } + } + } + Ok(normalized) +} + +fn write_external_atomic(output: &Path, bytes: &[u8]) -> Result<(), String> { + let parent = output + .parent() + .ok_or_else(|| format!("output path has no parent: {}", output.display()))?; + fs::create_dir_all(parent).map_err(|error| format!("create {}: {error}", parent.display()))?; + if fs::symlink_metadata(output).is_ok_and(|metadata| metadata.file_type().is_symlink()) { + return Err(format!( + "release provenance output cannot be a symlink: {}", + output.display() + )); + } + let mut temporary = tempfile::NamedTempFile::new_in(parent) + .map_err(|error| format!("create temporary provenance output: {error}"))?; + temporary + .write_all(bytes) + .map_err(|error| format!("write temporary provenance output: {error}"))?; + temporary + .as_file() + .sync_all() + .map_err(|error| format!("sync temporary provenance output: {error}"))?; + temporary + .persist(output) + .map_err(|error| format!("persist {}: {}", output.display(), error.error))?; + Ok(()) +} + +fn descriptor_for_file(workspace_root: &Path, relative: &str) -> Result<FileDescriptor, String> { + descriptor_for_bytes(relative, &read_regular_file(workspace_root, relative)?) +} + +fn descriptor_for_bytes(path: &str, bytes: &[u8]) -> Result<FileDescriptor, String> { + Ok(FileDescriptor { + path: path.to_owned(), + byte_length: bytes.len() as u64, + sha256: sha256_hex(bytes), + hash_algorithm: HASH_ALGORITHM.to_owned(), + }) +} + +fn load_toml<T: for<'de> Deserialize<'de>>( + workspace_root: &Path, + relative: &str, +) -> Result<T, String> { + let bytes = read_regular_file(workspace_root, relative)?; + let source = std::str::from_utf8(&bytes) + .map_err(|error| format!("{relative} must be UTF-8: {error}"))?; + toml::from_str(source).map_err(|error| format!("parse {relative}: {error}")) +} + +fn require_unique_nonempty(values: &[String], label: &str) -> Result<(), String> { + let mut unique = BTreeSet::new(); + for value in values { + if value.trim().is_empty() { + return Err(format!("{label} entries must not be empty")); + } + if !unique.insert(value) { + return Err(format!("{label} contains duplicate entry {value}")); + } + } + Ok(()) +} + +fn canonical_json_bytes<T: Serialize>(value: &T) -> Result<Vec<u8>, String> { + let mut bytes = + serde_json::to_vec_pretty(value).map_err(|error| format!("serialize JSON: {error}"))?; + bytes.push(b'\n'); + Ok(bytes) +} + +fn validate_json_schema(schema: &Value, instance: &Value) -> Result<(), String> { + let validator = jsonschema::validator_for(schema) + .map_err(|error| format!("compile {SCHEMA_RELATIVE}: {error}"))?; + let errors = validator + .iter_errors(instance) + .map(|error| error.to_string()) + .collect::<Vec<_>>(); + if errors.is_empty() { + Ok(()) + } else { + Err(format!( + "release provenance violates {SCHEMA_RELATIVE}: {}", + errors.join("; ") + )) + } +} + +fn sha256_hex(bytes: &[u8]) -> String { + hex::encode(Sha256::digest(bytes)) +} + +fn release_provenance_schema() -> Value { + json!({ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://radroots.org/contracts/releases/phase1-publication-release-provenance-v1.schema.json", + "title": "Radroots Phase 1 Publication Release Provenance", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "contract_id", "authority", "manifest_schema", "semantic_contract", "candidate", "source_digest", "toolchain", "lockfile", "dependency_graph", "feature_profiles", "packages", "commands"], + "properties": { + "schema_version": {"const": SCHEMA_VERSION}, + "contract_id": {"const": CONTRACT_ID}, + "authority": {"const": AUTHORITY}, + "manifest_schema": {"$ref": "#/$defs/file"}, + "semantic_contract": {"$ref": "#/$defs/file"}, + "candidate": { + "type": "object", "additionalProperties": false, + "required": ["commit_oid", "tree_oid", "object_format", "clean_worktree"], + "properties": { + "commit_oid": {"$ref": "#/$defs/git_oid"}, + "tree_oid": {"$ref": "#/$defs/git_oid"}, + "object_format": {"enum": ["sha1", "sha256"]}, + "clean_worktree": {"const": true} + } + }, + "source_digest": { + "type": "object", "additionalProperties": false, + "required": ["algorithm", "oid"], + "properties": { + "algorithm": {"enum": ["git_tree_sha1_v1", "git_tree_sha256_v1"]}, + "oid": {"$ref": "#/$defs/git_oid"} + } + }, + "toolchain": { + "type": "object", "additionalProperties": false, + "required": ["channel", "components", "targets", "contract"], + "properties": { + "channel": {"type": "string", "minLength": 1}, + "components": {"$ref": "#/$defs/nonempty_unique_strings"}, + "targets": {"$ref": "#/$defs/nonempty_unique_strings"}, + "contract": {"$ref": "#/$defs/file"} + } + }, + "lockfile": {"$ref": "#/$defs/file"}, + "dependency_graph": { + "type": "object", "additionalProperties": false, + "required": ["command", "normalization", "package_count", "node_count", "normalized_byte_length", "normalized_sha256", "hash_algorithm"], + "properties": { + "command": {"const": METADATA_COMMAND}, + "normalization": {"const": "cargo_metadata_semantic_graph_v1"}, + "package_count": {"type": "integer", "minimum": 1}, + "node_count": {"type": "integer", "minimum": 1}, + "normalized_byte_length": {"type": "integer", "minimum": 1}, + "normalized_sha256": {"$ref": "#/$defs/sha256"}, + "hash_algorithm": {"const": HASH_ALGORITHM} + } + }, + "feature_profiles": { + "type": "object", "additionalProperties": false, + "required": ["contract", "selected"], + "properties": { + "contract": {"$ref": "#/$defs/file"}, + "selected": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/feature_profile"}} + } + }, + "packages": { + "type": "object", "additionalProperties": false, + "required": ["release_version", "publish_policy", "archives"], + "properties": { + "release_version": {"type": "string", "minLength": 1}, + "publish_policy": {"$ref": "#/$defs/file"}, + "archives": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/archive"}} + } + }, + "commands": {"type": "array", "minItems": 4, "maxItems": 4, "items": {"$ref": "#/$defs/command"}} + }, + "$defs": { + "sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "git_oid": {"type": "string", "pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$"}, + "nonempty_unique_strings": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}}, + "file": { + "type": "object", "additionalProperties": false, + "required": ["path", "byte_length", "sha256", "hash_algorithm"], + "properties": { + "path": {"type": "string", "minLength": 1}, + "byte_length": {"type": "integer", "minimum": 1}, + "sha256": {"$ref": "#/$defs/sha256"}, + "hash_algorithm": {"const": HASH_ALGORITHM} + } + }, + "feature_profile": { + "type": "object", "additionalProperties": false, + "required": ["package", "no_default_features", "features", "test_threads"], + "properties": { + "package": {"type": "string", "minLength": 1}, + "no_default_features": {"type": "boolean"}, + "features": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}, + "test_threads": {"type": "integer", "minimum": 1} + } + }, + "archive": { + "type": "object", "additionalProperties": false, + "required": ["package", "version", "filename", "byte_length", "sha256", "hash_algorithm"], + "properties": { + "package": {"type": "string", "minLength": 1}, + "version": {"type": "string", "minLength": 1}, + "filename": {"type": "string", "pattern": "^[A-Za-z0-9_.-]+\\.crate$"}, + "byte_length": {"type": "integer", "minimum": 1}, + "sha256": {"$ref": "#/$defs/sha256"}, + "hash_algorithm": {"const": HASH_ALGORITHM} + } + }, + "command": { + "type": "object", "additionalProperties": false, + "required": ["purpose", "command", "required_result"], + "properties": { + "purpose": {"type": "string", "minLength": 1}, + "command": {"type": "string", "minLength": 1}, + "required_result": {"type": "string", "minLength": 1} + } + } + } + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn file(path: &str) -> FileDescriptor { + FileDescriptor { + path: path.to_owned(), + byte_length: 1, + sha256: "0".repeat(64), + hash_algorithm: HASH_ALGORITHM.to_owned(), + } + } + + fn sample_manifest() -> ReleaseProvenanceManifest { + ReleaseProvenanceManifest { + schema_version: SCHEMA_VERSION, + contract_id: CONTRACT_ID.to_owned(), + authority: AUTHORITY.to_owned(), + manifest_schema: file(SCHEMA_RELATIVE), + semantic_contract: file(SEMANTIC_CONTRACT_RELATIVE), + candidate: GitCandidate { + commit_oid: "0".repeat(40), + tree_oid: "1".repeat(40), + object_format: "sha1".to_owned(), + clean_worktree: true, + }, + source_digest: GitTreeDigest { + algorithm: "git_tree_sha1_v1".to_owned(), + oid: "1".repeat(40), + }, + toolchain: ToolchainEvidence { + channel: "1.97.0".to_owned(), + components: vec!["rustfmt".to_owned()], + targets: vec!["wasm32-unknown-unknown".to_owned()], + contract: file(TOOLCHAIN_RELATIVE), + }, + lockfile: file(LOCKFILE_RELATIVE), + dependency_graph: DependencyGraphEvidence { + command: METADATA_COMMAND.to_owned(), + normalization: "cargo_metadata_semantic_graph_v1".to_owned(), + package_count: 1, + node_count: 1, + normalized_byte_length: 1, + normalized_sha256: "2".repeat(64), + hash_algorithm: HASH_ALGORITHM.to_owned(), + }, + feature_profiles: FeatureProfileEvidence { + contract: file(FEATURE_PROFILES_RELATIVE), + selected: vec![FeatureProfile { + package: "alpha".to_owned(), + no_default_features: false, + features: Vec::new(), + test_threads: 1, + }], + }, + packages: PackageEvidence { + release_version: "1.2.3".to_owned(), + publish_policy: file(PUBLISH_POLICY_RELATIVE), + archives: vec![PackageArchive { + package: "alpha".to_owned(), + version: "1.2.3".to_owned(), + filename: "alpha-1.2.3.crate".to_owned(), + byte_length: 1, + sha256: "3".repeat(64), + hash_algorithm: HASH_ALGORITHM.to_owned(), + }], + }, + commands: required_commands(), + } + } + + fn test_policy() -> PublishPolicy { + PublishPolicy { + release: ReleaseVersion { + version: "1.2.3".to_owned(), + }, + classification: ReleaseClassification { + public: vec!["alpha".to_owned(), "beta".to_owned()], + internal: vec!["internal".to_owned()], + deferred: Vec::new(), + retired: Vec::new(), + yank_only: Vec::new(), + }, + publish_order: PublishOrder { + crates: vec!["alpha".to_owned(), "beta".to_owned()], + }, + } + } + + fn metadata(root: &str, dependency_package: &str) -> Value { + let alpha_id = format!("path+file://{root}/alpha#alpha@1.0.0"); + let beta_id = format!("path+file://{root}/beta#beta@1.0.0"); + json!({ + "version": 1, + "packages": [ + { + "name": "alpha", "version": "1.0.0", "id": alpha_id, + "source": null, "checksum": null, "edition": "2024", "rust_version": "1.97.0", + "features": {"default": []}, "dependencies": [], + "targets": [{"name": "alpha", "kind": ["lib"], "crate_types": ["lib"], "src_path": format!("{root}/alpha/src/lib.rs"), "edition": "2024", "doctest": true, "test": true, "doc": true}] + }, + { + "name": "beta", "version": "1.0.0", "id": beta_id, + "source": null, "checksum": null, "edition": "2024", "rust_version": "1.97.0", + "features": {}, + "dependencies": [{"name": "alpha", "source": null, "req": "*", "kind": null, "rename": null, "optional": false, "uses_default_features": true, "features": [], "target": null, "registry": null}], + "targets": [{"name": "beta", "kind": ["lib"], "crate_types": ["lib"], "src_path": format!("{root}/beta/src/lib.rs"), "edition": "2024", "doctest": true, "test": true, "doc": true}] + } + ], + "workspace_members": [format!("path+file://{root}/alpha#alpha@1.0.0"), format!("path+file://{root}/beta#beta@1.0.0")], + "workspace_default_members": [format!("path+file://{root}/alpha#alpha@1.0.0"), format!("path+file://{root}/beta#beta@1.0.0")], + "resolve": { + "root": null, + "nodes": [ + {"id": format!("path+file://{root}/alpha#alpha@1.0.0"), "deps": [], "features": ["default"]}, + {"id": format!("path+file://{root}/beta#beta@1.0.0"), "deps": [{"name": "alpha", "pkg": dependency_package, "dep_kinds": [{"kind": null, "target": null}]}], "features": []} + ] + } + }) + } + + #[test] + fn release_provenance_metadata_normalization_ignores_workspace_paths() { + let first = metadata( + "/first/worktree", + "path+file:///first/worktree/alpha#alpha@1.0.0", + ); + let second = metadata( + "/second/worktree", + "path+file:///second/worktree/alpha#alpha@1.0.0", + ); + assert_eq!( + normalize_metadata(&first).unwrap(), + normalize_metadata(&second).unwrap() + ); + + let mut changed = second; + changed["resolve"]["nodes"][1]["deps"] = json!([]); + assert_ne!( + normalize_metadata(&first).unwrap(), + normalize_metadata(&changed).unwrap() + ); + } + + #[test] + fn release_provenance_requires_complete_exact_package_archives() { + let temp = tempfile::TempDir::new().unwrap(); + fs::write(temp.path().join("alpha-1.2.3.crate"), b"alpha").unwrap(); + let missing = describe_package_archives(temp.path(), &test_policy()).unwrap_err(); + assert!(missing.contains("missing package archives: beta")); + + fs::write(temp.path().join("beta-1.2.3.crate"), b"beta").unwrap(); + let archives = describe_package_archives(temp.path(), &test_policy()).unwrap(); + assert_eq!(archives.len(), 2); + assert_eq!(archives[0].package, "alpha"); + + fs::write(temp.path().join("unknown-1.2.3.crate"), b"unknown").unwrap(); + let extra = describe_package_archives(temp.path(), &test_policy()).unwrap_err(); + assert!(extra.contains("unexpected package archive")); + } + + #[test] + fn release_provenance_rejects_dirty_git_candidate() { + let temp = tempfile::TempDir::new().unwrap(); + let git = |args: &[&str]| { + let status = Command::new("git") + .args(args) + .current_dir(temp.path()) + .status() + .unwrap(); + assert!(status.success()); + }; + git(&["init", "-q"]); + fs::write(temp.path().join("tracked"), b"clean\n").unwrap(); + git(&["add", "tracked"]); + git(&[ + "-c", + "user.name=Radroots Test", + "-c", + "user.email=test@radroots.invalid", + "commit", + "-q", + "-m", + "initial", + ]); + ensure_clean_git_worktree(temp.path()).unwrap(); + fs::write(temp.path().join("tracked"), b"dirty\n").unwrap(); + assert!( + ensure_clean_git_worktree(temp.path()) + .unwrap_err() + .contains("clean Git worktree") + ); + } + + #[test] + fn release_provenance_schema_rejects_unknown_fields() { + let schema = release_provenance_schema(); + jsonschema::validator_for(&schema).expect("schema compiles"); + let mut invalid = json!({"unexpected": true}); + let error = validate_json_schema(&schema, &invalid).unwrap_err(); + assert!(error.contains("release provenance violates")); + invalid.as_object_mut().unwrap().remove("unexpected"); + } + + #[test] + fn release_provenance_manifest_cross_fields_are_validated() { + let mut manifest = sample_manifest(); + validate_manifest(&manifest).expect("sample release provenance"); + + manifest.source_digest.oid = "4".repeat(40); + assert!( + validate_manifest(&manifest) + .unwrap_err() + .contains("Git identity") + ); + + manifest = sample_manifest(); + manifest.packages.archives[0].filename = "wrong.crate".to_owned(); + assert!( + validate_manifest(&manifest) + .unwrap_err() + .contains("same ordered public package set") + ); + } + + #[test] + fn release_provenance_rejects_unversioned_sources_outside_candidate_tree() { + let base = tempfile::TempDir::new().unwrap(); + let workspace = base.path().join("workspace"); + let external = base.path().join("external"); + fs::create_dir_all(&workspace).unwrap(); + fs::create_dir_all(&external).unwrap(); + fs::write(workspace.join("Cargo.toml"), b"[package]\nname='inside'\n").unwrap(); + fs::write(external.join("Cargo.toml"), b"[package]\nname='outside'\n").unwrap(); + let metadata = json!({ + "packages": [{ + "name": "outside", + "source": null, + "manifest_path": external.join("Cargo.toml") + }] + }); + let error = validate_metadata_source_roots(&workspace, &metadata).unwrap_err(); + assert!(error.contains("outside the candidate source tree")); + } + + #[cfg(unix)] + #[test] + fn release_provenance_resolves_output_parent_symlinks() { + use std::os::unix::fs::symlink; + + let base = tempfile::TempDir::new().unwrap(); + let workspace = base.path().join("workspace"); + fs::create_dir_all(&workspace).unwrap(); + let link = base.path().join("external-looking-link"); + symlink(&workspace, &link).unwrap(); + let error = require_output_outside_workspace(&workspace, &link.join("provenance.json")) + .unwrap_err(); + assert!(error.contains("outside the source worktree")); + + require_output_outside_workspace( + &workspace, + &base.path().join("actual-external/provenance.json"), + ) + .expect("real external output is accepted"); + } +} diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs @@ -24,9 +24,11 @@ fn usage() { eprintln!(" cargo xtask contract raw-source-rebuild-manifest [--write]"); eprintln!(" cargo xtask contract phase1-publication-artifact-manifest [--write]"); eprintln!(" cargo xtask contract phase1-publication-allowlist-manifest [--write]"); + eprintln!(" cargo xtask contract release-provenance-schema [--write]"); eprintln!(" cargo xtask contract knowledge-manifest [--write]"); eprintln!(" cargo xtask dto-roots --check|--write"); eprintln!(" cargo xtask release preflight"); + eprintln!(" cargo xtask release provenance --package-dir <dir> --out <outside-worktree-file>"); eprintln!(" cargo xtask coverage run-crate --crate <crate> [--out <dir>]"); eprintln!(" cargo xtask coverage required-crates"); eprintln!(" cargo xtask coverage workspace-crates"); @@ -86,10 +88,42 @@ fn release_preflight_at(root: &Path) -> Result<(), String> { fn run_release(args: &[String]) -> Result<(), String> { match args.first().map(String::as_str) { Some("preflight") => release_preflight(), + Some("provenance") => run_release_provenance(&args[1..]), _ => Err("unknown release subcommand".to_string()), } } +fn run_release_provenance(args: &[String]) -> Result<(), String> { + let mut package_directory = None; + let mut output = None; + let mut index = 0; + while index < args.len() { + match args[index].as_str() { + "--package-dir" if package_directory.is_none() => { + index += 1; + package_directory = args.get(index).map(PathBuf::from); + } + "--out" if output.is_none() => { + index += 1; + output = args.get(index).map(PathBuf::from); + } + unknown => { + return Err(format!( + "release provenance received unknown or duplicate argument {unknown}" + )); + } + } + index += 1; + } + let package_directory = package_directory.ok_or_else(|| { + "release provenance requires exactly --package-dir <dir> and --out <file>".to_owned() + })?; + let output = output.ok_or_else(|| { + "release provenance requires exactly --package-dir <dir> and --out <file>".to_owned() + })?; + contract::write_release_provenance(&workspace_root(), &package_directory, &output) +} + #[cfg_attr(coverage_nightly, coverage(off))] fn run_contract(args: &[String]) -> Result<(), String> { match args.first().map(String::as_str) { @@ -160,6 +194,15 @@ fn run_contract(args: &[String]) -> Result<(), String> { .to_string(), ), }, + Some("release-provenance-schema") => match &args[1..] { + [] => contract::validate_release_provenance_schema(&workspace_root()), + [flag] if flag == "--write" => { + contract::write_release_provenance_schema(&workspace_root()) + } + _ => { + Err("release-provenance-schema accepts no arguments or exactly --write".to_string()) + } + }, Some("knowledge-manifest") => { if args.get(1).map(String::as_str) == Some("--write") { contract::write_knowledge_contract_manifest(&workspace_root()) @@ -298,6 +341,20 @@ mod tests { ]) .expect_err("invalid Phase 1 publication allowlist manifest mode"); assert!(invalid_publication_allowlist.contains("exactly --write")); + let invalid_release_provenance_schema = run_contract(&[ + "release-provenance-schema".to_string(), + "--invalid".to_string(), + ]) + .expect_err("invalid release provenance schema mode"); + assert!(invalid_release_provenance_schema.contains("exactly --write")); + + let missing_release_provenance_args = + run_release(&["provenance".to_string()]).expect_err("missing provenance arguments"); + assert!(missing_release_provenance_args.contains("requires exactly")); + let unknown_release_provenance_arg = + run_release(&["provenance".to_string(), "--unknown".to_string()]) + .expect_err("unknown provenance argument"); + assert!(unknown_release_provenance_arg.contains("unknown or duplicate")); let unknown_root = run(&["unknown".to_string()]).expect_err("unknown command"); assert!(unknown_root.contains("unknown command")); @@ -403,6 +460,8 @@ mod tests { .expect("contract Phase 1 publication artifact manifest"); run_contract(&["phase1-publication-allowlist-manifest".to_string()]) .expect("contract Phase 1 publication allowlist manifest"); + run_contract(&["release-provenance-schema".to_string()]) + .expect("contract release provenance schema"); run_contract(&["knowledge-manifest".to_string()]).expect("contract knowledge manifest"); } }