commit 2cf1ed57c894376ca99bf3af4d3416feb8bcf093
parent f600dbf5bd5df8c2faa2b8e05ff7630bd043f5e1
Author: triesap <tyson@radroots.org>
Date: Wed, 22 Jul 2026 03:54:58 +0000
event-codec: enforce Phase 1 publication allowlist
- admit only the seven sealed publication leaves through typed and canonical adapters
- reproject kind profiles with strict post, calendar, and FoodAvailability rules
- execute 39 allowlist vectors while freezing registry-v7 and predecessor bytes
- govern the successor manifest, public documentation, and release metadata
Diffstat:
17 files changed, 6826 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -184,6 +184,21 @@ publish policy both pass for the same source revision.
encodings, cross-profile promotion, stale identifiers, media drift, and
digest tampering without claiming restored byte verification, upload
completion, signature authenticity, or signer authority.
+<!-- release-change: phase1-publication-allowlist -->
+- The event-codec publication surface now exposes an additive allowlist over
+ the sealed Phase 1 artifact. Its opaque output admits exactly supporting
+ Profile plus root Update, PhotoUpdate, Ask, typed date/time Event, and focused
+ FoodAvailability leaves. Registry-v7 reprojection enforces
+ Ask-before-PhotoUpdate-before-Update precedence and marker-partitions
+ kind-`30402` before profile validation. A canonical-JSON adapter composes
+ strict artifact reload with the same allowlist for durable consumers. Raw
+ and generic events, including raw date/time events, replies, comments,
+ deletion requests, calendar collections and RSVPs, BUD-11 authorizations,
+ ephemeral events, trade, commerce, group, and operations families cannot
+ produce its sealed input; deferred route/delivery product surfaces are
+ non-events and likewise fail closed. Event-contract registry v7 remains
+ byte-identical, and the new gate grants no signing, upload, retrieval, relay,
+ or entitlement authority.
- Bare-envelope replica ingestion is quarantined behind the explicit,
non-default `legacy-ingest` feature. Default replica APIs expose emit and sync
surfaces only; a future product ingest boundary must consume a store-produced
diff --git a/contracts/conformance/vectors/publication/phase1_allowlist.v1.json b/contracts/conformance/vectors/publication/phase1_allowlist.v1.json
@@ -0,0 +1,549 @@
+{
+ "contract_version": "1.0.0",
+ "suite": "phase1_publication_allowlist",
+ "vectors": [
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 0,
+ "leaf": "profile"
+ },
+ "id": "sealed_profile_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"profile\",\"authored_operation_id\":\"profile.build_authored_draft\",\"event_contract_id\":\"radroots.profile.metadata.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":0,\"tags\":[],\"content\":\"{\\\"name\\\":\\\"victoria-farm\\\",\\\"display_name\\\":\\\"Victoria Farm\\\",\\\"about\\\":\\\"Seasonal produce from the Saanich Peninsula\\\",\\\"picture\\\":\\\"https://media.example/512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a.png\\\",\\\"banner\\\":\\\"https://media.example/fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145.webp\\\",\\\"nip05\\\":\\\"farm@example.com\\\",\\\"bot\\\":false}\"},\"expected_event_id\":\"a7d081b9c142e3a68245bafe1921b0e0dd88f886406e7165a9729f6c57026289\",\"media_references\":[{\"url\":\"https://media.example/512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a.png\",\"sha256\":\"512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a\",\"size\":15,\"media_type\":\"image/png\"},{\"url\":\"https://media.example/fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145.webp\",\"sha256\":\"fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145\",\"size\":14,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"e296ed63312d58b4e3ee160d4ca3457eb922920d3116329ca498f08e402215b0\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "update"
+ },
+ "id": "sealed_update_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"update\",\"authored_operation_id\":\"social.update.build_authored_draft\",\"event_contract_id\":\"radroots.social.update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[],\"content\":\"Carrots harvested today\"},\"expected_event_id\":\"7f2d9dbbd6d2f3db1e83338ea2e669b2458e62ded159de15a3fa98dcf9f164e3\",\"media_references\":[],\"artifact_digest\":\"9ad318496bd4a710fbc7f3e0f6d5a01de808d352db91ca56a12e710904784cc5\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "photo_update"
+ },
+ "id": "sealed_photo_update_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"photo_update\",\"authored_operation_id\":\"social.photo_update.build_authored_draft\",\"event_contract_id\":\"radroots.social.photo_update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"Strawberries at the farm stand https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"e592229776cd2a0d0e25a701f4629c0a5dc6e08481a235623d50da6ee8e8f438\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"9ed6b7a6fcec3b07bd0c9179fd5b6ecf74517390051e3c2bd1659be62a26c197\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "ask"
+ },
+ "id": "sealed_ask_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"ask\",\"authored_operation_id\":\"social.ask.build_authored_draft\",\"event_contract_id\":\"radroots.social.ask.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"t\",\"radroots-ask\"],[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"When will strawberries be ready? https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"bd45fbbb5bfb8a5aa32df81d240e5f3bd56b0f8cde3a20b3f7d455801373fe48\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"41eddea199bba97adebef4ce433ebfa05d97978ce8464ed3d52762ab717c2a3c\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 31922,
+ "leaf": "event_date"
+ },
+ "id": "sealed_date_event_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_date\",\"authored_operation_id\":\"social.calendar_date_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.date_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31922,\"tags\":[[\"d\",\"farmers-market-2026\"],[\"title\",\"Moss Street Farmers Market\"],[\"start\",\"2026-07-25\"],[\"end\",\"2026-07-26\"],[\"location\",\"Victoria, BC\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"Saturday market in Victoria\"},\"expected_event_id\":\"bea82662b6acf2f1272c8335a24bb0957e35d22c2b29d3e30c094ad7398dc7c7\",\"media_references\":[{\"url\":\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\",\"sha256\":\"0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8\",\"size\":10,\"media_type\":\"image/jpeg\"}],\"artifact_digest\":\"0ec5162ab80988999aac62329d4cfc9c7efce6a82bbda539a723347d73596eb3\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 31923,
+ "leaf": "event_time"
+ },
+ "id": "sealed_time_event_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_time\",\"authored_operation_id\":\"social.calendar_time_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.time_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31923,\"tags\":[[\"d\",\"farm-tour-2026\"],[\"title\",\"Saanich Farm Tour\"],[\"start\",\"1785003600\"],[\"end\",\"1785007200\"],[\"D\",\"20659\"],[\"start_tzid\",\"America/Vancouver\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"A one-hour farm tour\"},\"expected_event_id\":\"2088ed11cd8c1495a82e6f5198ed2ee98e0cbc599f22d7f3380892155dc55894\",\"media_references\":[{\"url\":\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\",\"sha256\":\"0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8\",\"size\":10,\"media_type\":\"image/jpeg\"}],\"artifact_digest\":\"56bfb4bed87559a58922343905a73c0c96dd0a57f94ff0955caecf0589198638\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 30402,
+ "leaf": "food_availability"
+ },
+ "id": "sealed_food_availability_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"food_availability\",\"authored_operation_id\":\"food_availability.build_authored_draft\",\"event_contract_id\":\"radroots.food.availability.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":30402,\"tags\":[[\"d\",\"nantes-carrots\"],[\"title\",\"Nantes Carrots\"],[\"summary\",\"Fresh bunches\"],[\"published_at\",\"1784347140\"],[\"location\",\"Central Saanich, BC\"],[\"price\",\"3\",\"CAD\"],[\"radroots:price_unit\",\"lb\"],[\"radroots:quantity\",\"24\",\"lb\"],[\"status\",\"active\"],[\"image\",\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"1200x800\"]],\"content\":\"Fresh Nantes carrots available this week.\"},\"expected_event_id\":\"76b60f1b178c0dbdc14a87de36151ea6b04a866ce50e34a0ccae2f406f514e81\",\"media_references\":[{\"url\":\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"sha256\":\"8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e\",\"size\":14,\"media_type\":\"image/png\"}],\"artifact_digest\":\"f0989cb993194af4a2f907ad8337e50378d2757c536a09afb4055a681043a7d8\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 0,
+ "leaf": "profile"
+ },
+ "id": "canonical_profile_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"profile\",\"authored_operation_id\":\"profile.build_authored_draft\",\"event_contract_id\":\"radroots.profile.metadata.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":0,\"tags\":[],\"content\":\"{\\\"name\\\":\\\"victoria-farm\\\",\\\"display_name\\\":\\\"Victoria Farm\\\",\\\"about\\\":\\\"Seasonal produce from the Saanich Peninsula\\\",\\\"picture\\\":\\\"https://media.example/512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a.png\\\",\\\"banner\\\":\\\"https://media.example/fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145.webp\\\",\\\"nip05\\\":\\\"farm@example.com\\\",\\\"bot\\\":false}\"},\"expected_event_id\":\"a7d081b9c142e3a68245bafe1921b0e0dd88f886406e7165a9729f6c57026289\",\"media_references\":[{\"url\":\"https://media.example/512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a.png\",\"sha256\":\"512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a\",\"size\":15,\"media_type\":\"image/png\"},{\"url\":\"https://media.example/fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145.webp\",\"sha256\":\"fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145\",\"size\":14,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"e296ed63312d58b4e3ee160d4ca3457eb922920d3116329ca498f08e402215b0\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "update"
+ },
+ "id": "canonical_update_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"update\",\"authored_operation_id\":\"social.update.build_authored_draft\",\"event_contract_id\":\"radroots.social.update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[],\"content\":\"Carrots harvested today\"},\"expected_event_id\":\"7f2d9dbbd6d2f3db1e83338ea2e669b2458e62ded159de15a3fa98dcf9f164e3\",\"media_references\":[],\"artifact_digest\":\"9ad318496bd4a710fbc7f3e0f6d5a01de808d352db91ca56a12e710904784cc5\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "photo_update"
+ },
+ "id": "canonical_photo_update_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"photo_update\",\"authored_operation_id\":\"social.photo_update.build_authored_draft\",\"event_contract_id\":\"radroots.social.photo_update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"Strawberries at the farm stand https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"e592229776cd2a0d0e25a701f4629c0a5dc6e08481a235623d50da6ee8e8f438\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"9ed6b7a6fcec3b07bd0c9179fd5b6ecf74517390051e3c2bd1659be62a26c197\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "ask"
+ },
+ "id": "canonical_ask_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"ask\",\"authored_operation_id\":\"social.ask.build_authored_draft\",\"event_contract_id\":\"radroots.social.ask.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"t\",\"radroots-ask\"],[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"When will strawberries be ready? https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"bd45fbbb5bfb8a5aa32df81d240e5f3bd56b0f8cde3a20b3f7d455801373fe48\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"41eddea199bba97adebef4ce433ebfa05d97978ce8464ed3d52762ab717c2a3c\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 31922,
+ "leaf": "event_date"
+ },
+ "id": "canonical_date_event_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_date\",\"authored_operation_id\":\"social.calendar_date_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.date_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31922,\"tags\":[[\"d\",\"farmers-market-2026\"],[\"title\",\"Moss Street Farmers Market\"],[\"start\",\"2026-07-25\"],[\"end\",\"2026-07-26\"],[\"location\",\"Victoria, BC\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"Saturday market in Victoria\"},\"expected_event_id\":\"bea82662b6acf2f1272c8335a24bb0957e35d22c2b29d3e30c094ad7398dc7c7\",\"media_references\":[{\"url\":\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\",\"sha256\":\"0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8\",\"size\":10,\"media_type\":\"image/jpeg\"}],\"artifact_digest\":\"0ec5162ab80988999aac62329d4cfc9c7efce6a82bbda539a723347d73596eb3\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 31923,
+ "leaf": "event_time"
+ },
+ "id": "canonical_time_event_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_time\",\"authored_operation_id\":\"social.calendar_time_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.time_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31923,\"tags\":[[\"d\",\"farm-tour-2026\"],[\"title\",\"Saanich Farm Tour\"],[\"start\",\"1785003600\"],[\"end\",\"1785007200\"],[\"D\",\"20659\"],[\"start_tzid\",\"America/Vancouver\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"A one-hour farm tour\"},\"expected_event_id\":\"2088ed11cd8c1495a82e6f5198ed2ee98e0cbc599f22d7f3380892155dc55894\",\"media_references\":[{\"url\":\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\",\"sha256\":\"0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8\",\"size\":10,\"media_type\":\"image/jpeg\"}],\"artifact_digest\":\"56bfb4bed87559a58922343905a73c0c96dd0a57f94ff0955caecf0589198638\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 30402,
+ "leaf": "food_availability"
+ },
+ "id": "canonical_food_availability_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"food_availability\",\"authored_operation_id\":\"food_availability.build_authored_draft\",\"event_contract_id\":\"radroots.food.availability.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":30402,\"tags\":[[\"d\",\"nantes-carrots\"],[\"title\",\"Nantes Carrots\"],[\"summary\",\"Fresh bunches\"],[\"published_at\",\"1784347140\"],[\"location\",\"Central Saanich, BC\"],[\"price\",\"3\",\"CAD\"],[\"radroots:price_unit\",\"lb\"],[\"radroots:quantity\",\"24\",\"lb\"],[\"status\",\"active\"],[\"image\",\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"1200x800\"]],\"content\":\"Fresh Nantes carrots available this week.\"},\"expected_event_id\":\"76b60f1b178c0dbdc14a87de36151ea6b04a866ce50e34a0ccae2f406f514e81\",\"media_references\":[{\"url\":\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"sha256\":\"8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e\",\"size\":14,\"media_type\":\"image/png\"}],\"artifact_digest\":\"f0989cb993194af4a2f907ad8337e50378d2757c536a09afb4055a681043a7d8\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_post_profile_invalid"
+ },
+ "id": "update_claim_with_ask_marker_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"update\",\"authored_operation_id\":\"social.update.build_authored_draft\",\"event_contract_id\":\"radroots.social.update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"t\",\"radroots-ask\"]],\"content\":\"Carrots harvested today\"},\"expected_event_id\":\"bce463fddb9395ba11d4b8500379a39e14afd1472b3cf47e22b6b9f2a438353e\",\"media_references\":[],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_post_profile_invalid"
+ },
+ "id": "photo_claim_with_ask_marker_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"photo_update\",\"authored_operation_id\":\"social.photo_update.build_authored_draft\",\"event_contract_id\":\"radroots.social.photo_update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"t\",\"radroots-ask\"],[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"Strawberries at the farm stand https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"57afde82467e5d72aa07ee8cf1db454945a1615295e4e112d9ffd89e987c10f4\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_post_profile_invalid"
+ },
+ "id": "ask_claim_without_marker_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"ask\",\"authored_operation_id\":\"social.ask.build_authored_draft\",\"event_contract_id\":\"radroots.social.ask.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"When will strawberries be ready? https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"3f67e9ff31c26764bc1c74ef9cafd570a7a7454b3e3768be8f8c1a6175b3d7db\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_post_profile_invalid"
+ },
+ "id": "update_claim_with_photo_profile_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"update\",\"authored_operation_id\":\"social.update.build_authored_draft\",\"event_contract_id\":\"radroots.social.update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"Strawberries at the farm stand https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"e592229776cd2a0d0e25a701f4629c0a5dc6e08481a235623d50da6ee8e8f438\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_calendar_profile_invalid"
+ },
+ "id": "typed_date_event_with_generic_shape_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_date\",\"authored_operation_id\":\"social.calendar_date_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.date_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31922,\"tags\":[[\"d\",\"generic-calendar\"],[\"title\",\"Generic calendar event\"]],\"content\":\"Saturday market in Victoria\"},\"expected_event_id\":\"10e28f8f61a9bb9affa110bb6cce7cc8e371918aead30fbd31d41792db8f3325\",\"media_references\":[],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "family": "generic_calendar",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_calendar_profile_invalid"
+ },
+ "id": "typed_time_event_with_generic_shape_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_time\",\"authored_operation_id\":\"social.calendar_time_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.time_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31923,\"tags\":[[\"d\",\"generic-calendar\"],[\"title\",\"Generic calendar event\"]],\"content\":\"A one-hour farm tour\"},\"expected_event_id\":\"c3da57e519122044ee663cece433d56f0b5d967f8e78a1a1a80260ae7653d6bd\",\"media_references\":[],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "family": "generic_calendar",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_food_profile_invalid"
+ },
+ "id": "operational_listing_profile_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"food_availability\",\"authored_operation_id\":\"food_availability.build_authored_draft\",\"event_contract_id\":\"radroots.food.availability.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":30402,\"tags\":[[\"d\",\"nantes-carrots\"],[\"title\",\"Nantes Carrots\"],[\"summary\",\"Fresh bunches\"],[\"published_at\",\"1784347140\"],[\"location\",\"Central Saanich, BC\"],[\"price\",\"3\",\"CAD\"],[\"status\",\"active\"],[\"image\",\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"1200x800\"],[\"radroots:price\",\"3\",\"CAD\"]],\"content\":\"Fresh Nantes carrots available this week.\"},\"expected_event_id\":\"d752583639bf9edaa5ab25e491be5b565b91af5fb01167377d36fdb319dd61d7\",\"media_references\":[{\"url\":\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"sha256\":\"8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e\",\"size\":14,\"media_type\":\"image/png\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "family": "operational_listing",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_food_profile_invalid"
+ },
+ "id": "generic_nip99_profile_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"food_availability\",\"authored_operation_id\":\"food_availability.build_authored_draft\",\"event_contract_id\":\"radroots.food.availability.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":30402,\"tags\":[[\"d\",\"nantes-carrots\"],[\"title\",\"Nantes Carrots\"],[\"summary\",\"Fresh bunches\"],[\"published_at\",\"1784347140\"],[\"location\",\"Central Saanich, BC\"],[\"price\",\"3\",\"CAD\"],[\"status\",\"active\"],[\"image\",\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"1200x800\"]],\"content\":\"Fresh Nantes carrots available this week.\"},\"expected_event_id\":\"6ae040461fe7e6967bb48b2861bc1dbeac075f0331027e2d04b9618e2d752a2d\",\"media_references\":[{\"url\":\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"sha256\":\"8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e\",\"size\":14,\"media_type\":\"image/png\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "family": "generic_nip99",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_food_profile_invalid"
+ },
+ "id": "mixed_food_and_operational_markers_are_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"food_availability\",\"authored_operation_id\":\"food_availability.build_authored_draft\",\"event_contract_id\":\"radroots.food.availability.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":30402,\"tags\":[[\"d\",\"nantes-carrots\"],[\"title\",\"Nantes Carrots\"],[\"summary\",\"Fresh bunches\"],[\"published_at\",\"1784347140\"],[\"location\",\"Central Saanich, BC\"],[\"price\",\"3\",\"CAD\"],[\"radroots:price_unit\",\"lb\"],[\"radroots:quantity\",\"24\",\"lb\"],[\"status\",\"active\"],[\"image\",\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"1200x800\"],[\"radroots:price\",\"3\",\"CAD\"]],\"content\":\"Fresh Nantes carrots available this week.\"},\"expected_event_id\":\"5302117c4c89252c40b57601fe8b115b779bd9256235f449ad86c50ca97c8219\",\"media_references\":[{\"url\":\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"sha256\":\"8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e\",\"size\":14,\"media_type\":\"image/png\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "family": "mixed_classified_listing",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "unsealed_profile_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":0,\"tags\":[],\"content\":\"{\\\"name\\\":\\\"victoria-farm\\\",\\\"display_name\\\":\\\"Victoria Farm\\\",\\\"about\\\":\\\"Seasonal produce from the Saanich Peninsula\\\",\\\"picture\\\":\\\"https://media.example/512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a.png\\\",\\\"banner\\\":\\\"https://media.example/fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145.webp\\\",\\\"nip05\\\":\\\"farm@example.com\\\",\\\"bot\\\":false}\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 0,
+ "family": "unsealed_profile",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "generic_root_note_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":1,\"tags\":[],\"content\":\"Generic root note\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 1,
+ "family": "generic_root_note",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "reply_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"e\",\"1111111111111111111111111111111111111111111111111111111111111111\",\"\",\"reply\"]],\"content\":\"Reply\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 1,
+ "family": "reply",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "comment_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":1111,\"tags\":[[\"E\",\"1111111111111111111111111111111111111111111111111111111111111111\"]],\"content\":\"Comment\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 1111,
+ "family": "comment",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "deletion_request_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":5,\"tags\":[[\"e\",\"1111111111111111111111111111111111111111111111111111111111111111\"]],\"content\":\"Removed\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 5,
+ "family": "deletion_request",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "raw_date_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":31922,\"tags\":[[\"d\",\"farmers-market-2026\"],[\"title\",\"Moss Street Farmers Market\"],[\"start\",\"2026-07-25\"],[\"end\",\"2026-07-26\"],[\"location\",\"Victoria, BC\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"Saturday market in Victoria\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 31922,
+ "family": "unsealed_calendar_event",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "raw_time_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":31923,\"tags\":[[\"d\",\"farm-tour-2026\"],[\"title\",\"Saanich Farm Tour\"],[\"start\",\"1785003600\"],[\"end\",\"1785007200\"],[\"D\",\"20659\"],[\"start_tzid\",\"America/Vancouver\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"A one-hour farm tour\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 31923,
+ "family": "unsealed_calendar_event",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "calendar_collection_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":31924,\"tags\":[[\"d\",\"victoria-calendar\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 31924,
+ "family": "calendar_collection",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "calendar_rsvp_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":31925,\"tags\":[[\"a\",\"31922:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:market\"]],\"content\":\"accepted\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 31925,
+ "family": "calendar_rsvp",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "bud11_authorization_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":24242,\"tags\":[[\"t\",\"upload\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 24242,
+ "family": "bud11_authorization",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "ephemeral_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":20001,\"tags\":[],\"content\":\"Ephemeral\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 20001,
+ "family": "ephemeral_event",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "trade_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":3470,\"tags\":[[\"d\",\"trade-proposal\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 3470,
+ "family": "trade",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "order_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":3422,\"tags\":[[\"d\",\"order-request\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 3422,
+ "family": "commerce_order",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "route_delivery_family_is_rejected",
+ "input": {
+ "canonical_json": "{\"product_surface\":\"route_delivery\"}",
+ "family": "route_delivery",
+ "surface": "non_event_product_surface"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "group_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":9007,\"tags\":[[\"h\",\"victoria-growers\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 9007,
+ "family": "group",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "operations_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":78,\"tags\":[[\"d\",\"internal-operation\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 78,
+ "family": "operations",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ }
+ ]
+}
diff --git a/contracts/operations.toml b/contracts/operations.toml
@@ -106,6 +106,9 @@ public = [
"RadrootsPhase1PublicationArtifactDigest",
"RadrootsPhase1PublicationArtifact",
"RadrootsPhase1PublicationArtifactError",
+ "RadrootsPhase1PublicationLeaf",
+ "RadrootsPhase1AllowlistedPublicationArtifact",
+ "RadrootsPhase1PublicationAllowlistError",
"RadrootsFarm",
"RadrootsOperationalListing",
"RadrootsPost",
@@ -906,6 +909,69 @@ case_kinds = [
"publication_artifact.from_canonical_json.invalid",
]
+[operations.phase1_publication_allowlist_allow_artifact]
+domain = "publication"
+id = "publication_allowlist.allow_artifact"
+stability = "beta"
+inputs = ["RadrootsPhase1PublicationArtifact"]
+outputs = ["RadrootsPhase1AllowlistedPublicationArtifact"]
+error_class = "validation_error"
+deterministic = true
+signing = "none"
+transport = "none"
+
+[operations.phase1_publication_allowlist_allow_artifact.implementation]
+rust_modules = [
+ "crates/event_codec/src/food_availability/inbound/registry_v7.rs",
+ "crates/event_codec/src/post/inbound/registry_v7.rs",
+ "crates/event_codec/src/wire/publication.rs",
+ "crates/event_codec/src/wire/publication/allowlist.rs",
+]
+rust_types = [
+ "radroots_event_codec::wire::publication::RadrootsPhase1PublicationArtifact",
+ "radroots_event_codec::wire::publication::allowlist::RadrootsPhase1AllowlistedPublicationArtifact",
+ "radroots_event_codec::wire::publication::allowlist::RadrootsPhase1PublicationAllowlistError",
+ "radroots_event_codec::wire::publication::allowlist::RadrootsPhase1PublicationLeaf",
+]
+
+[operations.phase1_publication_allowlist_allow_artifact.conformance]
+vector = "contracts/conformance/vectors/publication/phase1_allowlist.v1.json"
+case_kinds = [
+ "publication_allowlist.allow_artifact.valid",
+]
+
+[operations.phase1_publication_allowlist_allow_canonical_json]
+domain = "publication"
+id = "publication_allowlist.allow_canonical_json"
+stability = "beta"
+inputs = ["Bytes"]
+outputs = ["RadrootsPhase1AllowlistedPublicationArtifact"]
+error_class = "validation_error"
+deterministic = true
+signing = "none"
+transport = "none"
+
+[operations.phase1_publication_allowlist_allow_canonical_json.implementation]
+rust_modules = [
+ "crates/event_codec/src/food_availability/inbound/registry_v7.rs",
+ "crates/event_codec/src/post/inbound/registry_v7.rs",
+ "crates/event_codec/src/wire/publication.rs",
+ "crates/event_codec/src/wire/publication/allowlist.rs",
+]
+rust_types = [
+ "radroots_event_codec::wire::publication::RadrootsPhase1PublicationArtifact",
+ "radroots_event_codec::wire::publication::allowlist::RadrootsPhase1AllowlistedPublicationArtifact",
+ "radroots_event_codec::wire::publication::allowlist::RadrootsPhase1PublicationAllowlistError",
+ "radroots_event_codec::wire::publication::allowlist::RadrootsPhase1PublicationLeaf",
+]
+
+[operations.phase1_publication_allowlist_allow_canonical_json.conformance]
+vector = "contracts/conformance/vectors/publication/phase1_allowlist.v1.json"
+case_kinds = [
+ "publication_allowlist.allow_canonical_json.valid",
+ "publication_allowlist.allow_canonical_json.invalid",
+]
+
[operations.profile_build_authored_draft]
domain = "profile"
id = "profile.build_authored_draft"
diff --git a/contracts/releases/1.0.0-alpha.1.toml b/contracts/releases/1.0.0-alpha.1.toml
@@ -288,6 +288,17 @@ semver_impacts = [
summary = "Add the authored-only Phase 1 publication artifact with seven typed leaves, exact top-level NIP-01 id placement, explicit canonical-byte encode/decode operations, bounded signed-event and artifact wires, complete Blossom URL commitments, and a single-NUL domain-separated digest."
[[changes]]
+id = "phase1-publication-allowlist"
+classification = "feature"
+semver_impacts = [
+ "add_exported_type",
+ "add_exported_function",
+ "add_exported_constant",
+ "add_conformance_vector",
+]
+summary = "Add an opaque publication-lane allowlist and canonical-JSON durable-reload adapter over sealed Phase 1 artifacts with one supporting Profile leaf and six root leaves, strict same-kind reprojection, typed-only date/time Event handling, focused FoodAvailability marker partitioning, and fail-closed exclusion of every other generic, ephemeral, or deferred product family."
+
+[[changes]]
id = "event-store-validity-visibility-split"
classification = "breaking"
semver_impacts = [
diff --git a/crates/event_codec/README b/crates/event_codec/README
@@ -7,6 +7,24 @@ are `profile`, `update`, `photo_update`, `ask`, `event_date`, `event_time`, and
`food_availability`; Event remains one semantic role with typed date and time
subvariants.
+`wire::publication::allowlist::allow_phase1_publication_artifact` is the
+additive durable-publication gate over that sealed artifact. It consumes the
+artifact and returns an opaque allowlisted typestate carrying exactly one of
+the same seven leaves: supporting Profile or one of the six root publication
+forms. The companion
+`allow_phase1_publication_canonical_json` operation reloads persisted canonical
+artifact bytes through the same gate. The gate revalidates the artifact, then
+reprojects kind `1` with Ask-before-PhotoUpdate-before-Update precedence,
+requires the strict typed date/time NIP-52 profiles, and marker-partitions kind
+`30402` before accepting focused FoodAvailability. Raw or generic events,
+including raw date/time events, replies, comments, deletion requests, calendar
+collections and RSVPs, BUD-11 authorizations, ephemeral events, trade,
+commerce, groups, and operations cannot produce the sealed input and fail
+closed at canonical reload; deferred route/delivery product surfaces are
+non-events and likewise fail closed. This allowlist does not change
+event-contract registry v7 or grant signing, media readiness, transport, relay,
+or entitlement authority.
+
Persistence crosses only the explicit `to_canonical_json` and
`from_canonical_json` operations. Schema version 1 orders the envelope as
`schema_version`, `semantic_variant`, `authored_operation_id`,
diff --git a/crates/event_codec/contracts/phase1_publication_allowlist_v1.descriptor.json b/crates/event_codec/contracts/phase1_publication_allowlist_v1.descriptor.json
@@ -0,0 +1,24 @@
+{
+ "contract_id": "radroots_event_codec.phase1_publication_allowlist_v1",
+ "event_contract_registry_v7_sha256": "91595544310f865bdef064ee760c227c870417a95b87b3e27278f8da74fdddea",
+ "manifest": {
+ "byte_length": 86551,
+ "hash_algorithm": "sha256_bytes_v1",
+ "path": "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.json",
+ "sha256": "5ba2e7d6cdd616b423d3bdbcf2c6d2644d4f2568b1eb13cbaacf101a076f485c"
+ },
+ "manifest_schema": {
+ "byte_length": 8439,
+ "hash_algorithm": "sha256_bytes_v1",
+ "path": "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.schema.json",
+ "sha256": "5bdccda1449ca6837d9922171983b880e81324cb0bdefc32e1d92b387f692a4f"
+ },
+ "manifest_sidecar": {
+ "byte_length": 65,
+ "hash_algorithm": "sha256_bytes_v1",
+ "path": "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.sha256",
+ "sha256": "3741e277ad9b80a00a611be5ecbb49419e61bd7949b0efa02727cd60f58b81f7"
+ },
+ "predecessor_manifest_sha256": "0776e1d84c9366954047e75cdf12d9acc9a7108260157c3534f22067075a385a",
+ "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
@@ -0,0 +1,2307 @@
+{
+ "schema_version": 1,
+ "contract_id": "radroots_event_codec.phase1_publication_allowlist_v1",
+ "authority_id": "phase1_publication_allowlist_v1",
+ "manifest_schema": {
+ "path": "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.schema.json",
+ "byte_length": 8439,
+ "sha256": "5bdccda1449ca6837d9922171983b880e81324cb0bdefc32e1d92b387f692a4f",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ "predecessor": {
+ "contract_id": "radroots_event_codec.phase1_publication_artifact_v1",
+ "immutable_artifacts": [
+ {
+ "path": "crates/event_codec/contracts/phase1_publication_artifact_v1.manifest.json",
+ "byte_length": 89464,
+ "sha256": "0776e1d84c9366954047e75cdf12d9acc9a7108260157c3534f22067075a385a",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "crates/event_codec/contracts/phase1_publication_artifact_v1.manifest.schema.json",
+ "byte_length": 11972,
+ "sha256": "1d72cee2754e7ac45105d79b1ecf7d44251991be7a18ba106166e962000e8320",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "crates/event_codec/contracts/phase1_publication_artifact_v1.manifest.sha256",
+ "byte_length": 65,
+ "sha256": "586c0985b502f22241b4d90f2ecb475d43953fc3ddd66d6fbfa3b3eb9cf34444",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "contracts/conformance/vectors/publication/phase1_artifact.v1.json",
+ "byte_length": 23113,
+ "sha256": "ec18c687d5b0710a48624ddb620d89157e6b645dbea8bb91c62e3a111d20c622",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "crates/event_codec/tests/fixtures/phase1_publication_artifact.v1.json",
+ "byte_length": 23113,
+ "sha256": "ec18c687d5b0710a48624ddb620d89157e6b645dbea8bb91c62e3a111d20c622",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "path": "crates/event_codec/tests/publication_artifact.rs",
+ "byte_length": 34582,
+ "sha256": "7a31169eac4217a38cb3ef25eb9213f2f89e11fb17e76ceaf7449b34225e98af",
+ "hash_algorithm": "sha256_bytes_v1"
+ }
+ ]
+ },
+ "event_contract_registry": {
+ "version": 7,
+ "inventory": {
+ "path": "contracts/event_store/event_contract_registry_v7.inventory.json",
+ "byte_length": 158021,
+ "sha256": "91595544310f865bdef064ee760c227c870417a95b87b3e27278f8da74fdddea",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ "sidecar": {
+ "path": "contracts/event_store/event_contract_registry_v7.inventory.sha256",
+ "byte_length": 65,
+ "sha256": "823081e6f423c149865521b185a8517aea35ce4470fa77c165ad7ca5207f036b",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ "evolution": "immutable_registry_v7_plus_additive_phase1_publication_allowlist_v1"
+ },
+ "allowlist": {
+ "version": 1,
+ "contract_id": "radroots.phase1.publication_allowlist.v1",
+ "operation_id": "publication_allowlist.allow_artifact",
+ "canonical_json_operation_id": "publication_allowlist.allow_canonical_json",
+ "input_type": "RadrootsPhase1PublicationArtifact",
+ "output_type": "RadrootsPhase1AllowlistedPublicationArtifact",
+ "allowed_leaves": [
+ {
+ "leaf": "profile",
+ "kind": 0,
+ "publication_role": "supporting",
+ "authored_operation_id": "profile.build_authored_draft",
+ "event_contract_id": "radroots.profile.metadata.v1"
+ },
+ {
+ "leaf": "update",
+ "kind": 1,
+ "publication_role": "root",
+ "authored_operation_id": "social.update.build_authored_draft",
+ "event_contract_id": "radroots.social.update.v1"
+ },
+ {
+ "leaf": "photo_update",
+ "kind": 1,
+ "publication_role": "root",
+ "authored_operation_id": "social.photo_update.build_authored_draft",
+ "event_contract_id": "radroots.social.photo_update.v1"
+ },
+ {
+ "leaf": "ask",
+ "kind": 1,
+ "publication_role": "root",
+ "authored_operation_id": "social.ask.build_authored_draft",
+ "event_contract_id": "radroots.social.ask.v1"
+ },
+ {
+ "leaf": "event_date",
+ "kind": 31922,
+ "publication_role": "root",
+ "authored_operation_id": "social.calendar_date_event.build_authored_draft",
+ "event_contract_id": "radroots.calendar.date_event.v1"
+ },
+ {
+ "leaf": "event_time",
+ "kind": 31923,
+ "publication_role": "root",
+ "authored_operation_id": "social.calendar_time_event.build_authored_draft",
+ "event_contract_id": "radroots.calendar.time_event.v1"
+ },
+ {
+ "leaf": "food_availability",
+ "kind": 30402,
+ "publication_role": "root",
+ "authored_operation_id": "food_availability.build_authored_draft",
+ "event_contract_id": "radroots.food.availability.v1"
+ }
+ ],
+ "kind_one_precedence": [
+ "ask",
+ "photo_update",
+ "update"
+ ],
+ "event_policy": "strict_typed_nip52_date_or_time_artifact_only_v1",
+ "classified_listing_policy": "raw_marker_partition_before_focused_food_profile_validation_v1",
+ "denied_families": [
+ "unsealed_profile",
+ "unsealed_calendar_event",
+ "generic_root_note",
+ "reply",
+ "comment",
+ "deletion_request",
+ "generic_calendar",
+ "calendar_collection",
+ "calendar_rsvp",
+ "bud11_authorization",
+ "ephemeral_event",
+ "operational_listing",
+ "generic_nip99",
+ "mixed_classified_listing",
+ "trade",
+ "commerce_order",
+ "route_delivery",
+ "group",
+ "operations"
+ ],
+ "granted_capability": "phase1_durable_publication_lane_entry_only_v1",
+ "excluded_capabilities": [
+ "signing",
+ "media_upload",
+ "media_retrieval",
+ "media_readiness",
+ "relay_transport",
+ "product_entitlement",
+ "inbound_registry_admission"
+ ]
+ },
+ "predecessor_source_supersessions": [
+ "CHANGELOG.md",
+ "contracts/operations.toml",
+ "contracts/releases/1.0.0-alpha.1.toml",
+ "crates/event_codec/README",
+ "crates/event_codec/src/wire/publication.rs",
+ "tools/xtask/src/contract.rs",
+ "tools/xtask/src/contract/phase1_publication_artifact.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": 30938,
+ "sha256": "45fdb845ae0f231cdb0e94ce49c472dab64ff4bbecc79d751c23a3d813708932",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "workspace_lockfile_authority",
+ "path": "Cargo.lock",
+ "byte_length": 216965,
+ "sha256": "f26bf62f77e48914c89c15e689fdbc6799928e9603cab38f50c0c65a0c405edf",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "workspace_manifest_authority",
+ "path": "Cargo.toml",
+ "byte_length": 10836,
+ "sha256": "285532dbb0894204843a832880f136ceac5ee312a3203ff951fb0551fac63ec4",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "operations_authority",
+ "path": "contracts/operations.toml",
+ "byte_length": 78556,
+ "sha256": "3c96d953e208b1781b391a722414861a859c106101ccf8d7b7789193f197d7eb",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "release_authority",
+ "path": "contracts/releases/1.0.0-alpha.1.toml",
+ "byte_length": 20917,
+ "sha256": "42c472fd5927d976c108f4d7204da2d34ff13de2066fa36d3b2e5f038a274df4",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "blossom_manifest_authority",
+ "path": "crates/blossom/Cargo.toml",
+ "byte_length": 901,
+ "sha256": "9e84a9f1820e84994e5c8f705cae243eadd4676244f98604c53fc0800f0e51b3",
+ "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": 17227,
+ "sha256": "8b0b9557cca68604791c9bb080f3cb0ff81a2397efb7f1ab600b2b4f4f7f8fa7",
+ "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": 1335,
+ "sha256": "61ebee86f02887c45507bab052686da082f74ae26f23d18ff4019e02c70097bd",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "public_production_source",
+ "path": "crates/blossom/src/url.rs",
+ "byte_length": 15364,
+ "sha256": "05325325da6627ba48318851fb4e61a9ad540aad37fc1c27ac9a47f27300891f",
+ "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",
+ "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": 481424,
+ "sha256": "e1f5867b5861b5f2a7dd69e22d41c8abc8e484458d0a19b8659c81501897d2cd",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "superseded_nip09_contract_governance",
+ "path": "tools/xtask/src/contract/nip09_reconciliation.rs",
+ "byte_length": 852432,
+ "sha256": "971d75198770265b17e17df23ba17a6096ad687d1cf2ac0f662a2c190ab39f94",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "publication_allowlist_contract_governance",
+ "path": "tools/xtask/src/contract/phase1_publication_allowlist.rs",
+ "byte_length": 76855,
+ "sha256": "9d6459123f78d3de282fafa5b12daf253c79c378da2b225ae780a69d56784a42",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "publication_contract_governance",
+ "path": "tools/xtask/src/contract/phase1_publication_artifact.rs",
+ "byte_length": 75139,
+ "sha256": "3f0df4f48ee2d2dcd6943c591bba746d2ebb4c179b1825563e15d8b33302cddf",
+ "hash_algorithm": "sha256_bytes_v1"
+ },
+ {
+ "role": "superseded_raw_rebuild_contract_governance",
+ "path": "tools/xtask/src/contract/raw_source_rebuild.rs",
+ "byte_length": 297547,
+ "sha256": "e8cf84ba8f27432d0ba7aefa01fc61f9e37810d48f407fa2b9c7c969e5c76724",
+ "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",
+ "sealed_update_is_allowed",
+ "sealed_photo_update_is_allowed",
+ "sealed_ask_is_allowed",
+ "sealed_date_event_is_allowed",
+ "sealed_time_event_is_allowed",
+ "sealed_food_availability_is_allowed",
+ "canonical_profile_is_allowed",
+ "canonical_update_is_allowed",
+ "canonical_photo_update_is_allowed",
+ "canonical_ask_is_allowed",
+ "canonical_date_event_is_allowed",
+ "canonical_time_event_is_allowed",
+ "canonical_food_availability_is_allowed"
+ ],
+ "invalid_case_ids": [
+ "update_claim_with_ask_marker_is_rejected",
+ "photo_claim_with_ask_marker_is_rejected",
+ "ask_claim_without_marker_is_rejected",
+ "update_claim_with_photo_profile_is_rejected",
+ "typed_date_event_with_generic_shape_is_rejected",
+ "typed_time_event_with_generic_shape_is_rejected",
+ "operational_listing_profile_is_rejected",
+ "generic_nip99_profile_is_rejected",
+ "mixed_food_and_operational_markers_are_rejected",
+ "unsealed_profile_is_rejected",
+ "generic_root_note_is_rejected",
+ "reply_is_rejected",
+ "comment_is_rejected",
+ "deletion_request_is_rejected",
+ "raw_date_event_is_rejected",
+ "raw_time_event_is_rejected",
+ "calendar_collection_is_rejected",
+ "calendar_rsvp_is_rejected",
+ "bud11_authorization_is_rejected",
+ "ephemeral_event_is_rejected",
+ "trade_event_is_rejected",
+ "order_event_is_rejected",
+ "route_delivery_family_is_rejected",
+ "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
@@ -0,0 +1,352 @@
+{
+ "$defs": {
+ "file": {
+ "additionalProperties": false,
+ "properties": {
+ "byte_length": {
+ "minimum": 1,
+ "type": "integer"
+ },
+ "hash_algorithm": {
+ "const": "sha256_bytes_v1"
+ },
+ "path": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "sha256": {
+ "pattern": "^[0-9a-f]{64}$",
+ "type": "string"
+ }
+ },
+ "required": [
+ "path",
+ "byte_length",
+ "sha256",
+ "hash_algorithm"
+ ],
+ "type": "object"
+ },
+ "leaf": {
+ "additionalProperties": false,
+ "properties": {
+ "authored_operation_id": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "event_contract_id": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "kind": {
+ "minimum": 0,
+ "type": "integer"
+ },
+ "leaf": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "publication_role": {
+ "enum": [
+ "supporting",
+ "root"
+ ]
+ }
+ },
+ "required": [
+ "leaf",
+ "kind",
+ "publication_role",
+ "authored_operation_id",
+ "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",
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "additionalProperties": false,
+ "properties": {
+ "allowlist": {
+ "additionalProperties": false,
+ "properties": {
+ "allowed_leaves": {
+ "items": {
+ "$ref": "#/$defs/leaf"
+ },
+ "maxItems": 7,
+ "minItems": 7,
+ "type": "array"
+ },
+ "canonical_json_operation_id": {
+ "const": "publication_allowlist.allow_canonical_json"
+ },
+ "classified_listing_policy": {
+ "const": "raw_marker_partition_before_focused_food_profile_validation_v1"
+ },
+ "contract_id": {
+ "const": "radroots.phase1.publication_allowlist.v1"
+ },
+ "denied_families": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "maxItems": 19,
+ "minItems": 19,
+ "type": "array"
+ },
+ "event_policy": {
+ "const": "strict_typed_nip52_date_or_time_artifact_only_v1"
+ },
+ "excluded_capabilities": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "maxItems": 7,
+ "minItems": 7,
+ "type": "array"
+ },
+ "granted_capability": {
+ "const": "phase1_durable_publication_lane_entry_only_v1"
+ },
+ "input_type": {
+ "const": "RadrootsPhase1PublicationArtifact"
+ },
+ "kind_one_precedence": {
+ "const": [
+ "ask",
+ "photo_update",
+ "update"
+ ]
+ },
+ "operation_id": {
+ "const": "publication_allowlist.allow_artifact"
+ },
+ "output_type": {
+ "const": "RadrootsPhase1AllowlistedPublicationArtifact"
+ },
+ "version": {
+ "const": 1
+ }
+ },
+ "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"
+ ],
+ "type": "object"
+ },
+ "authority_id": {
+ "const": "phase1_publication_allowlist_v1"
+ },
+ "contract_id": {
+ "const": "radroots_event_codec.phase1_publication_allowlist_v1"
+ },
+ "event_contract_registry": {
+ "additionalProperties": false,
+ "properties": {
+ "evolution": {
+ "const": "immutable_registry_v7_plus_additive_phase1_publication_allowlist_v1"
+ },
+ "inventory": {
+ "$ref": "#/$defs/file"
+ },
+ "sidecar": {
+ "$ref": "#/$defs/file"
+ },
+ "version": {
+ "const": 7
+ }
+ },
+ "required": [
+ "version",
+ "inventory",
+ "sidecar",
+ "evolution"
+ ],
+ "type": "object"
+ },
+ "manifest_schema": {
+ "$ref": "#/$defs/file"
+ },
+ "predecessor": {
+ "additionalProperties": false,
+ "properties": {
+ "contract_id": {
+ "const": "radroots_event_codec.phase1_publication_artifact_v1"
+ },
+ "immutable_artifacts": {
+ "items": {
+ "$ref": "#/$defs/file"
+ },
+ "maxItems": 6,
+ "minItems": 6,
+ "type": "array"
+ }
+ },
+ "required": [
+ "contract_id",
+ "immutable_artifacts"
+ ],
+ "type": "object"
+ },
+ "predecessor_source_supersessions": {
+ "items": {
+ "minLength": 1,
+ "type": "string"
+ },
+ "maxItems": 8,
+ "minItems": 8,
+ "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"
+ }
+ },
+ "required": [
+ "record_path",
+ "change_id",
+ "changelog_path",
+ "changelog_marker"
+ ],
+ "type": "object"
+ },
+ "result_vector": {
+ "additionalProperties": false,
+ "properties": {
+ "byte_length": {
+ "minimum": 1,
+ "type": "integer"
+ },
+ "canonical_path": {
+ "const": "contracts/conformance/vectors/publication/phase1_allowlist.v1.json"
+ },
+ "executor_path": {
+ "const": "crates/event_codec/tests/publication_allowlist.rs"
+ },
+ "executor_test": {
+ "const": "publication_allowlist_conformance_vector_executes_every_case"
+ },
+ "hash_algorithm": {
+ "const": "sha256_bytes_v1"
+ },
+ "invalid_case_ids": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 25,
+ "minItems": 25,
+ "type": "array"
+ },
+ "mirror_path": {
+ "const": "crates/event_codec/tests/fixtures/phase1_publication_allowlist.v1.json"
+ },
+ "sha256": {
+ "pattern": "^[0-9a-f]{64}$",
+ "type": "string"
+ },
+ "valid_case_ids": {
+ "items": {
+ "type": "string"
+ },
+ "maxItems": 14,
+ "minItems": 14,
+ "type": "array"
+ }
+ },
+ "required": [
+ "canonical_path",
+ "mirror_path",
+ "byte_length",
+ "sha256",
+ "hash_algorithm",
+ "executor_path",
+ "executor_test",
+ "valid_case_ids",
+ "invalid_case_ids"
+ ],
+ "type": "object"
+ },
+ "schema_version": {
+ "const": 1
+ },
+ "source_files": {
+ "items": {
+ "$ref": "#/$defs/source"
+ },
+ "minItems": 1,
+ "type": "array"
+ }
+ },
+ "required": [
+ "schema_version",
+ "contract_id",
+ "authority_id",
+ "manifest_schema",
+ "predecessor",
+ "event_contract_registry",
+ "allowlist",
+ "predecessor_source_supersessions",
+ "source_files",
+ "result_vector",
+ "release"
+ ],
+ "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
@@ -0,0 +1 @@
+5ba2e7d6cdd616b423d3bdbcf2c6d2644d4f2568b1eb13cbaacf101a076f485c
diff --git a/crates/event_codec/src/wire/publication.rs b/crates/event_codec/src/wire/publication.rs
@@ -11,6 +11,8 @@
//! signer, or control the host/database. NIP-01 id and signature verification
//! remain the cryptographic authenticity boundary.
+pub mod allowlist;
+
#[cfg(not(feature = "std"))]
use alloc::{
format,
diff --git a/crates/event_codec/src/wire/publication/allowlist.rs b/crates/event_codec/src/wire/publication/allowlist.rs
@@ -0,0 +1,544 @@
+//! Strict publication allowlist for sealed Phase 1 artifacts.
+//!
+//! This authority is additive to event-contract registry v7. It answers only
+//! whether a sealed authored artifact may enter the Phase 1 durable
+//! publication lane. It does not broaden inbound admission, sign an event,
+//! prove media readiness, publish to a relay, or grant product entitlement.
+
+use core::fmt;
+
+use radroots_event::{
+ RadrootsEventTags,
+ classified_listing::RadrootsClassifiedListingPartition,
+ kinds::{
+ KIND_CALENDAR_DATE_EVENT, KIND_CALENDAR_TIME_EVENT, KIND_CLASSIFIED_LISTING, KIND_POST,
+ KIND_PROFILE,
+ },
+};
+
+use crate::{
+ food_availability::inbound::{
+ RadrootsFoodAvailabilityProjectionOutcome,
+ registry_v7::project_inbound_food_availability_parts,
+ },
+ post::inbound::{RadrootsPostClassification, registry_v7::project_inbound_post_parts},
+};
+
+use super::{
+ RadrootsPhase1PublicationArtifact, RadrootsPhase1PublicationArtifactError,
+ RadrootsPhase1PublicationEventVariant, RadrootsPhase1PublicationSemanticVariant,
+ validate_calendar_date, validate_calendar_time, validate_food_availability,
+ validate_phase1_publication_artifact, validate_profile,
+};
+
+pub const RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_VERSION: u32 = 1;
+pub const RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_CONTRACT_ID: &str =
+ "radroots.phase1.publication_allowlist.v1";
+pub const RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_REGISTRY_VERSION: u32 = 7;
+
+/// The exact seven authored leaves accepted by the Phase 1 publication lane.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum RadrootsPhase1PublicationLeaf {
+ Profile,
+ Update,
+ PhotoUpdate,
+ Ask,
+ EventDate,
+ EventTime,
+ FoodAvailability,
+}
+
+impl RadrootsPhase1PublicationLeaf {
+ pub const fn as_str(self) -> &'static str {
+ match self {
+ Self::Profile => "profile",
+ Self::Update => "update",
+ Self::PhotoUpdate => "photo_update",
+ Self::Ask => "ask",
+ Self::EventDate => "event_date",
+ Self::EventTime => "event_time",
+ Self::FoodAvailability => "food_availability",
+ }
+ }
+
+ pub const fn kind(self) -> u32 {
+ self.semantic_variant().kind()
+ }
+
+ pub const fn authored_operation_id(self) -> &'static str {
+ self.semantic_variant().authored_operation_id()
+ }
+
+ pub const fn event_contract_id(self) -> &'static str {
+ self.semantic_variant().event_contract_id()
+ }
+
+ const fn from_semantic_variant(variant: RadrootsPhase1PublicationSemanticVariant) -> Self {
+ match variant {
+ RadrootsPhase1PublicationSemanticVariant::Profile => Self::Profile,
+ RadrootsPhase1PublicationSemanticVariant::Update => Self::Update,
+ RadrootsPhase1PublicationSemanticVariant::PhotoUpdate => Self::PhotoUpdate,
+ RadrootsPhase1PublicationSemanticVariant::Ask => Self::Ask,
+ RadrootsPhase1PublicationSemanticVariant::Event(
+ RadrootsPhase1PublicationEventVariant::Date,
+ ) => Self::EventDate,
+ RadrootsPhase1PublicationSemanticVariant::Event(
+ RadrootsPhase1PublicationEventVariant::Time,
+ ) => Self::EventTime,
+ RadrootsPhase1PublicationSemanticVariant::FoodAvailability => Self::FoodAvailability,
+ }
+ }
+
+ const fn semantic_variant(self) -> RadrootsPhase1PublicationSemanticVariant {
+ match self {
+ Self::Profile => RadrootsPhase1PublicationSemanticVariant::Profile,
+ Self::Update => RadrootsPhase1PublicationSemanticVariant::Update,
+ Self::PhotoUpdate => RadrootsPhase1PublicationSemanticVariant::PhotoUpdate,
+ Self::Ask => RadrootsPhase1PublicationSemanticVariant::Ask,
+ Self::EventDate => RadrootsPhase1PublicationSemanticVariant::Event(
+ RadrootsPhase1PublicationEventVariant::Date,
+ ),
+ Self::EventTime => RadrootsPhase1PublicationSemanticVariant::Event(
+ RadrootsPhase1PublicationEventVariant::Time,
+ ),
+ Self::FoodAvailability => RadrootsPhase1PublicationSemanticVariant::FoodAvailability,
+ }
+ }
+}
+
+impl fmt::Display for RadrootsPhase1PublicationLeaf {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str(self.as_str())
+ }
+}
+
+/// A sealed artifact that passed the additive Phase 1 publication allowlist.
+///
+/// Private fields prevent callers from pairing a leaf with a different
+/// artifact. Construction is available only through
+/// [`allow_phase1_publication_artifact`] and its strict canonical-reload adapter
+/// [`allow_phase1_publication_canonical_json`].
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct RadrootsPhase1AllowlistedPublicationArtifact {
+ artifact: RadrootsPhase1PublicationArtifact,
+ leaf: RadrootsPhase1PublicationLeaf,
+}
+
+impl RadrootsPhase1AllowlistedPublicationArtifact {
+ pub const fn leaf(&self) -> RadrootsPhase1PublicationLeaf {
+ self.leaf
+ }
+
+ pub fn artifact(&self) -> &RadrootsPhase1PublicationArtifact {
+ &self.artifact
+ }
+
+ pub fn into_artifact(self) -> RadrootsPhase1PublicationArtifact {
+ self.artifact
+ }
+}
+
+/// Revalidates and reprojects a sealed artifact before granting Phase 1
+/// publication-lane authority.
+///
+/// Kind `1` is classified with Ask-before-PhotoUpdate-before-Update
+/// precedence. Calendar kinds must retain their strict date/time authored
+/// profile, and kind `30402` is marker-partitioned before focused Food profile
+/// validation. Every other kind and product family fails closed.
+pub fn allow_phase1_publication_artifact(
+ artifact: RadrootsPhase1PublicationArtifact,
+) -> Result<RadrootsPhase1AllowlistedPublicationArtifact, RadrootsPhase1PublicationAllowlistError> {
+ validate_phase1_publication_artifact(&artifact)
+ .map_err(RadrootsPhase1PublicationAllowlistError::ArtifactInvalid)?;
+
+ let claimed = RadrootsPhase1PublicationLeaf::from_semantic_variant(artifact.semantic_variant());
+ let projected = reproject_publication_leaf(&artifact)?;
+ if claimed != projected {
+ return Err(
+ RadrootsPhase1PublicationAllowlistError::SemanticVariantMismatch { claimed, projected },
+ );
+ }
+
+ Ok(RadrootsPhase1AllowlistedPublicationArtifact {
+ artifact,
+ leaf: projected,
+ })
+}
+
+/// Reloads exact canonical artifact bytes before applying the Phase 1
+/// publication allowlist.
+///
+/// This is the durable-reload boundary for persisted `09a0a` artifacts. Raw
+/// NIP-01 event JSON and malformed or semantically invalid artifact candidates
+/// fail in the predecessor parser and are returned as
+/// [`RadrootsPhase1PublicationAllowlistError::ArtifactInvalid`].
+pub fn allow_phase1_publication_canonical_json(
+ bytes: &[u8],
+) -> Result<RadrootsPhase1AllowlistedPublicationArtifact, RadrootsPhase1PublicationAllowlistError> {
+ let artifact = RadrootsPhase1PublicationArtifact::from_canonical_json(bytes)
+ .map_err(RadrootsPhase1PublicationAllowlistError::ArtifactInvalid)?;
+ allow_phase1_publication_artifact(artifact)
+}
+
+fn reproject_publication_leaf(
+ artifact: &RadrootsPhase1PublicationArtifact,
+) -> Result<RadrootsPhase1PublicationLeaf, RadrootsPhase1PublicationAllowlistError> {
+ let draft = artifact.draft();
+ match draft.kind() {
+ KIND_PROFILE => {
+ validate_profile(draft, artifact.media_references())
+ .map_err(|_| RadrootsPhase1PublicationAllowlistError::ProfileInvalid)?;
+ Ok(RadrootsPhase1PublicationLeaf::Profile)
+ }
+ KIND_POST => {
+ let projection =
+ project_inbound_post_parts(draft.kind(), draft.tags(), draft.content()).map_err(
+ |error| RadrootsPhase1PublicationAllowlistError::PostInvalid {
+ source_code: error.code(),
+ },
+ )?;
+ if !projection.diagnostics().is_empty() {
+ return Err(RadrootsPhase1PublicationAllowlistError::PostInvalid {
+ source_code: "post_projection_diagnostic",
+ });
+ }
+ match projection.classification() {
+ RadrootsPostClassification::Ask => Ok(RadrootsPhase1PublicationLeaf::Ask),
+ RadrootsPostClassification::PhotoUpdate => {
+ Ok(RadrootsPhase1PublicationLeaf::PhotoUpdate)
+ }
+ RadrootsPostClassification::Update => Ok(RadrootsPhase1PublicationLeaf::Update),
+ RadrootsPostClassification::ThreadExcluded => {
+ Err(RadrootsPhase1PublicationAllowlistError::PostExcluded)
+ }
+ }
+ }
+ KIND_CALENDAR_DATE_EVENT => {
+ validate_calendar_date(draft, artifact.media_references())
+ .map_err(|_| RadrootsPhase1PublicationAllowlistError::EventInvalid)?;
+ Ok(RadrootsPhase1PublicationLeaf::EventDate)
+ }
+ KIND_CALENDAR_TIME_EVENT => {
+ validate_calendar_time(draft, artifact.media_references())
+ .map_err(|_| RadrootsPhase1PublicationAllowlistError::EventInvalid)?;
+ Ok(RadrootsPhase1PublicationLeaf::EventTime)
+ }
+ KIND_CLASSIFIED_LISTING => reproject_food_availability(artifact),
+ kind => Err(RadrootsPhase1PublicationAllowlistError::UnsupportedKind { kind }),
+ }
+}
+
+fn reproject_food_availability(
+ artifact: &RadrootsPhase1PublicationArtifact,
+) -> Result<RadrootsPhase1PublicationLeaf, RadrootsPhase1PublicationAllowlistError> {
+ let draft = artifact.draft();
+ let tags = RadrootsEventTags::new(draft.tags().to_vec())
+ .map_err(|_| RadrootsPhase1PublicationAllowlistError::FoodInvalid)?;
+ let projection = project_inbound_food_availability_parts(
+ draft.kind(),
+ draft.created_at(),
+ &tags,
+ draft.content(),
+ )
+ .map_err(
+ |error| RadrootsPhase1PublicationAllowlistError::FoodInvalidWithSource {
+ source_code: error.code(),
+ },
+ )?;
+ match projection {
+ RadrootsFoodAvailabilityProjectionOutcome::Focused(projection) => {
+ if !projection.diagnostics().is_empty() {
+ return Err(RadrootsPhase1PublicationAllowlistError::FoodInvalid);
+ }
+ validate_food_availability(draft, artifact.media_references())
+ .map_err(|_| RadrootsPhase1PublicationAllowlistError::FoodInvalid)?;
+ Ok(RadrootsPhase1PublicationLeaf::FoodAvailability)
+ }
+ RadrootsFoodAvailabilityProjectionOutcome::Excluded(partition) => {
+ Err(RadrootsPhase1PublicationAllowlistError::FoodExcluded { partition })
+ }
+ }
+}
+
+#[non_exhaustive]
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum RadrootsPhase1PublicationAllowlistError {
+ ArtifactInvalid(RadrootsPhase1PublicationArtifactError),
+ UnsupportedKind {
+ kind: u32,
+ },
+ ProfileInvalid,
+ PostInvalid {
+ source_code: &'static str,
+ },
+ PostExcluded,
+ EventInvalid,
+ FoodInvalid,
+ FoodInvalidWithSource {
+ source_code: &'static str,
+ },
+ FoodExcluded {
+ partition: RadrootsClassifiedListingPartition,
+ },
+ SemanticVariantMismatch {
+ claimed: RadrootsPhase1PublicationLeaf,
+ projected: RadrootsPhase1PublicationLeaf,
+ },
+}
+
+impl RadrootsPhase1PublicationAllowlistError {
+ pub const fn code(&self) -> &'static str {
+ match self {
+ Self::ArtifactInvalid(_) => "publication_allowlist_artifact_invalid",
+ Self::UnsupportedKind { .. } => "publication_allowlist_kind_unsupported",
+ Self::ProfileInvalid => "publication_allowlist_profile_invalid",
+ Self::PostInvalid { .. } => "publication_allowlist_post_invalid",
+ Self::PostExcluded => "publication_allowlist_post_excluded",
+ Self::EventInvalid => "publication_allowlist_event_invalid",
+ Self::FoodInvalid | Self::FoodInvalidWithSource { .. } => {
+ "publication_allowlist_food_invalid"
+ }
+ Self::FoodExcluded { .. } => "publication_allowlist_food_excluded",
+ Self::SemanticVariantMismatch { .. } => {
+ "publication_allowlist_semantic_variant_mismatch"
+ }
+ }
+ }
+
+ pub const fn source_code(&self) -> Option<&'static str> {
+ match self {
+ Self::ArtifactInvalid(error) => Some(error.code()),
+ Self::PostInvalid { source_code } => Some(*source_code),
+ Self::FoodInvalidWithSource { source_code } => Some(*source_code),
+ _ => None,
+ }
+ }
+}
+
+impl fmt::Display for RadrootsPhase1PublicationAllowlistError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::UnsupportedKind { kind } => {
+ write!(
+ formatter,
+ "event kind {kind} is outside the Phase 1 publication allowlist"
+ )
+ }
+ Self::FoodExcluded { partition } => write!(
+ formatter,
+ "classified-listing partition {partition:?} is outside the Phase 1 publication allowlist"
+ ),
+ Self::SemanticVariantMismatch { claimed, projected } => write!(
+ formatter,
+ "publication artifact claims {claimed} but reprojects as {projected}"
+ ),
+ error => formatter.write_str(error.code()),
+ }
+ }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for RadrootsPhase1PublicationAllowlistError {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ match self {
+ Self::ArtifactInvalid(error) => Some(error),
+ _ => None,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde_json::Value;
+
+ const PREDECESSOR_VECTOR: &str =
+ include_str!("../../../tests/fixtures/phase1_publication_artifact.v1.json");
+
+ #[test]
+ fn publication_allowlist_admits_exactly_the_seven_sealed_leaves() {
+ let fixtures = [
+ ("profile", RadrootsPhase1PublicationLeaf::Profile),
+ ("update", RadrootsPhase1PublicationLeaf::Update),
+ ("photo_update", RadrootsPhase1PublicationLeaf::PhotoUpdate),
+ ("ask", RadrootsPhase1PublicationLeaf::Ask),
+ ("event_date", RadrootsPhase1PublicationLeaf::EventDate),
+ ("event_time", RadrootsPhase1PublicationLeaf::EventTime),
+ (
+ "food_availability",
+ RadrootsPhase1PublicationLeaf::FoodAvailability,
+ ),
+ ];
+
+ for (fixture, expected_leaf) in fixtures {
+ let artifact = artifact_fixture(fixture);
+ let canonical = artifact.to_canonical_json();
+ let allowed = allow_phase1_publication_artifact(artifact).unwrap();
+ assert_eq!(allowed.leaf(), expected_leaf);
+ assert_eq!(allowed.leaf().as_str(), fixture);
+ assert_eq!(allowed.leaf().kind(), allowed.artifact().draft().kind());
+ assert_eq!(
+ allowed.leaf().authored_operation_id(),
+ allowed.artifact().authored_operation_id()
+ );
+ assert_eq!(
+ allowed.leaf().event_contract_id(),
+ allowed.artifact().event_contract_id()
+ );
+ assert_eq!(allowed.into_artifact().to_canonical_json(), canonical);
+ }
+ }
+
+ #[test]
+ fn publication_allowlist_canonical_reload_uses_the_same_authority() {
+ for fixture in [
+ "profile",
+ "update",
+ "photo_update",
+ "ask",
+ "event_date",
+ "event_time",
+ "food_availability",
+ ] {
+ let artifact = artifact_fixture(fixture);
+ let expected = allow_phase1_publication_artifact(artifact.clone()).unwrap();
+ let actual =
+ allow_phase1_publication_canonical_json(&artifact.to_canonical_json()).unwrap();
+ assert_eq!(actual, expected);
+ }
+
+ let raw_event = br#"{"id":"00","pubkey":"00","created_at":1,"kind":1,"tags":[],"content":"raw","sig":"00"}"#;
+ assert_eq!(
+ allow_phase1_publication_canonical_json(raw_event)
+ .unwrap_err()
+ .source_code(),
+ Some("publication_artifact_invalid_json")
+ );
+ }
+
+ #[test]
+ fn publication_allowlist_reprojects_kind_one_with_exact_precedence() {
+ let update = artifact_fixture("update");
+ assert_eq!(
+ reproject_publication_leaf(&update).unwrap(),
+ RadrootsPhase1PublicationLeaf::Update
+ );
+
+ let photo = artifact_fixture("photo_update");
+ assert_eq!(
+ reproject_publication_leaf(&photo).unwrap(),
+ RadrootsPhase1PublicationLeaf::PhotoUpdate
+ );
+
+ let ask = artifact_fixture("ask");
+ assert_eq!(
+ reproject_publication_leaf(&ask).unwrap(),
+ RadrootsPhase1PublicationLeaf::Ask
+ );
+
+ let mut ask_over_photo = photo;
+ ask_over_photo
+ .draft
+ .tags
+ .insert(0, vec!["t".to_owned(), "radroots-ask".to_owned()]);
+ assert_eq!(
+ reproject_publication_leaf(&ask_over_photo).unwrap(),
+ RadrootsPhase1PublicationLeaf::Ask
+ );
+
+ let mut reply = update;
+ reply.draft.tags = vec![vec!["e".to_owned(), "00".repeat(32)]];
+ assert_eq!(
+ reproject_publication_leaf(&reply).unwrap_err(),
+ RadrootsPhase1PublicationAllowlistError::PostExcluded
+ );
+ }
+
+ #[test]
+ fn publication_allowlist_partitions_food_markers_before_profile_validation() {
+ let focused = artifact_fixture("food_availability");
+ assert_eq!(
+ reproject_publication_leaf(&focused).unwrap(),
+ RadrootsPhase1PublicationLeaf::FoodAvailability
+ );
+
+ let mut generic = focused.clone();
+ generic.draft.tags.retain(|tag| {
+ !matches!(
+ tag.first().map(String::as_str),
+ Some("radroots:price_unit" | "radroots:quantity")
+ )
+ });
+ assert_eq!(
+ reproject_publication_leaf(&generic).unwrap_err(),
+ RadrootsPhase1PublicationAllowlistError::FoodExcluded {
+ partition: RadrootsClassifiedListingPartition::GenericNip99,
+ }
+ );
+
+ let mut operational = generic;
+ operational.draft.tags.push(vec![
+ "radroots:price".to_owned(),
+ "3".to_owned(),
+ "CAD".to_owned(),
+ ]);
+ assert_eq!(
+ reproject_publication_leaf(&operational).unwrap_err(),
+ RadrootsPhase1PublicationAllowlistError::FoodExcluded {
+ partition: RadrootsClassifiedListingPartition::OperationalListing,
+ }
+ );
+
+ let mut mixed = focused;
+ mixed.draft.tags.push(vec![
+ "radroots:price".to_owned(),
+ "3".to_owned(),
+ "CAD".to_owned(),
+ ]);
+ assert_eq!(
+ reproject_publication_leaf(&mixed).unwrap_err().code(),
+ "publication_allowlist_food_invalid"
+ );
+ }
+
+ #[test]
+ fn publication_allowlist_rejects_generic_calendar_and_unsupported_kinds() {
+ let mut generic_calendar = artifact_fixture("event_date");
+ generic_calendar.draft.tags = vec![
+ vec!["d".to_owned(), "generic-calendar".to_owned()],
+ vec!["title".to_owned(), "Generic calendar event".to_owned()],
+ ];
+ assert_eq!(
+ reproject_publication_leaf(&generic_calendar).unwrap_err(),
+ RadrootsPhase1PublicationAllowlistError::EventInvalid
+ );
+
+ let mut bud11 = artifact_fixture("update");
+ bud11.draft.kind = 24_242;
+ assert_eq!(
+ reproject_publication_leaf(&bud11).unwrap_err(),
+ RadrootsPhase1PublicationAllowlistError::UnsupportedKind { kind: 24_242 }
+ );
+
+ let mut ephemeral = artifact_fixture("update");
+ ephemeral.draft.kind = 20_001;
+ assert_eq!(
+ reproject_publication_leaf(&ephemeral).unwrap_err(),
+ RadrootsPhase1PublicationAllowlistError::UnsupportedKind { kind: 20_001 }
+ );
+ }
+
+ fn artifact_fixture(name: &str) -> RadrootsPhase1PublicationArtifact {
+ let suite: Value = serde_json::from_str(PREDECESSOR_VECTOR).unwrap();
+ let vector = suite["vectors"]
+ .as_array()
+ .unwrap()
+ .iter()
+ .find(|vector| vector["expected"]["semantic_variant"].as_str() == Some(name))
+ .unwrap_or_else(|| panic!("missing predecessor fixture {name}"));
+ let canonical = vector["expected"]["canonical_json"]
+ .as_str()
+ .unwrap_or_else(|| panic!("missing canonical predecessor fixture {name}"));
+ RadrootsPhase1PublicationArtifact::from_canonical_json(canonical.as_bytes()).unwrap()
+ }
+}
diff --git a/crates/event_codec/tests/fixtures/phase1_publication_allowlist.v1.json b/crates/event_codec/tests/fixtures/phase1_publication_allowlist.v1.json
@@ -0,0 +1,549 @@
+{
+ "contract_version": "1.0.0",
+ "suite": "phase1_publication_allowlist",
+ "vectors": [
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 0,
+ "leaf": "profile"
+ },
+ "id": "sealed_profile_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"profile\",\"authored_operation_id\":\"profile.build_authored_draft\",\"event_contract_id\":\"radroots.profile.metadata.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":0,\"tags\":[],\"content\":\"{\\\"name\\\":\\\"victoria-farm\\\",\\\"display_name\\\":\\\"Victoria Farm\\\",\\\"about\\\":\\\"Seasonal produce from the Saanich Peninsula\\\",\\\"picture\\\":\\\"https://media.example/512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a.png\\\",\\\"banner\\\":\\\"https://media.example/fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145.webp\\\",\\\"nip05\\\":\\\"farm@example.com\\\",\\\"bot\\\":false}\"},\"expected_event_id\":\"a7d081b9c142e3a68245bafe1921b0e0dd88f886406e7165a9729f6c57026289\",\"media_references\":[{\"url\":\"https://media.example/512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a.png\",\"sha256\":\"512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a\",\"size\":15,\"media_type\":\"image/png\"},{\"url\":\"https://media.example/fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145.webp\",\"sha256\":\"fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145\",\"size\":14,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"e296ed63312d58b4e3ee160d4ca3457eb922920d3116329ca498f08e402215b0\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "update"
+ },
+ "id": "sealed_update_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"update\",\"authored_operation_id\":\"social.update.build_authored_draft\",\"event_contract_id\":\"radroots.social.update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[],\"content\":\"Carrots harvested today\"},\"expected_event_id\":\"7f2d9dbbd6d2f3db1e83338ea2e669b2458e62ded159de15a3fa98dcf9f164e3\",\"media_references\":[],\"artifact_digest\":\"9ad318496bd4a710fbc7f3e0f6d5a01de808d352db91ca56a12e710904784cc5\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "photo_update"
+ },
+ "id": "sealed_photo_update_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"photo_update\",\"authored_operation_id\":\"social.photo_update.build_authored_draft\",\"event_contract_id\":\"radroots.social.photo_update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"Strawberries at the farm stand https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"e592229776cd2a0d0e25a701f4629c0a5dc6e08481a235623d50da6ee8e8f438\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"9ed6b7a6fcec3b07bd0c9179fd5b6ecf74517390051e3c2bd1659be62a26c197\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "ask"
+ },
+ "id": "sealed_ask_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"ask\",\"authored_operation_id\":\"social.ask.build_authored_draft\",\"event_contract_id\":\"radroots.social.ask.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"t\",\"radroots-ask\"],[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"When will strawberries be ready? https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"bd45fbbb5bfb8a5aa32df81d240e5f3bd56b0f8cde3a20b3f7d455801373fe48\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"41eddea199bba97adebef4ce433ebfa05d97978ce8464ed3d52762ab717c2a3c\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 31922,
+ "leaf": "event_date"
+ },
+ "id": "sealed_date_event_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_date\",\"authored_operation_id\":\"social.calendar_date_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.date_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31922,\"tags\":[[\"d\",\"farmers-market-2026\"],[\"title\",\"Moss Street Farmers Market\"],[\"start\",\"2026-07-25\"],[\"end\",\"2026-07-26\"],[\"location\",\"Victoria, BC\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"Saturday market in Victoria\"},\"expected_event_id\":\"bea82662b6acf2f1272c8335a24bb0957e35d22c2b29d3e30c094ad7398dc7c7\",\"media_references\":[{\"url\":\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\",\"sha256\":\"0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8\",\"size\":10,\"media_type\":\"image/jpeg\"}],\"artifact_digest\":\"0ec5162ab80988999aac62329d4cfc9c7efce6a82bbda539a723347d73596eb3\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 31923,
+ "leaf": "event_time"
+ },
+ "id": "sealed_time_event_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_time\",\"authored_operation_id\":\"social.calendar_time_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.time_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31923,\"tags\":[[\"d\",\"farm-tour-2026\"],[\"title\",\"Saanich Farm Tour\"],[\"start\",\"1785003600\"],[\"end\",\"1785007200\"],[\"D\",\"20659\"],[\"start_tzid\",\"America/Vancouver\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"A one-hour farm tour\"},\"expected_event_id\":\"2088ed11cd8c1495a82e6f5198ed2ee98e0cbc599f22d7f3380892155dc55894\",\"media_references\":[{\"url\":\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\",\"sha256\":\"0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8\",\"size\":10,\"media_type\":\"image/jpeg\"}],\"artifact_digest\":\"56bfb4bed87559a58922343905a73c0c96dd0a57f94ff0955caecf0589198638\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 30402,
+ "leaf": "food_availability"
+ },
+ "id": "sealed_food_availability_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"food_availability\",\"authored_operation_id\":\"food_availability.build_authored_draft\",\"event_contract_id\":\"radroots.food.availability.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":30402,\"tags\":[[\"d\",\"nantes-carrots\"],[\"title\",\"Nantes Carrots\"],[\"summary\",\"Fresh bunches\"],[\"published_at\",\"1784347140\"],[\"location\",\"Central Saanich, BC\"],[\"price\",\"3\",\"CAD\"],[\"radroots:price_unit\",\"lb\"],[\"radroots:quantity\",\"24\",\"lb\"],[\"status\",\"active\"],[\"image\",\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"1200x800\"]],\"content\":\"Fresh Nantes carrots available this week.\"},\"expected_event_id\":\"76b60f1b178c0dbdc14a87de36151ea6b04a866ce50e34a0ccae2f406f514e81\",\"media_references\":[{\"url\":\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"sha256\":\"8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e\",\"size\":14,\"media_type\":\"image/png\"}],\"artifact_digest\":\"f0989cb993194af4a2f907ad8337e50378d2757c536a09afb4055a681043a7d8\"}",
+ "surface": "sealed_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_artifact.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 0,
+ "leaf": "profile"
+ },
+ "id": "canonical_profile_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"profile\",\"authored_operation_id\":\"profile.build_authored_draft\",\"event_contract_id\":\"radroots.profile.metadata.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":0,\"tags\":[],\"content\":\"{\\\"name\\\":\\\"victoria-farm\\\",\\\"display_name\\\":\\\"Victoria Farm\\\",\\\"about\\\":\\\"Seasonal produce from the Saanich Peninsula\\\",\\\"picture\\\":\\\"https://media.example/512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a.png\\\",\\\"banner\\\":\\\"https://media.example/fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145.webp\\\",\\\"nip05\\\":\\\"farm@example.com\\\",\\\"bot\\\":false}\"},\"expected_event_id\":\"a7d081b9c142e3a68245bafe1921b0e0dd88f886406e7165a9729f6c57026289\",\"media_references\":[{\"url\":\"https://media.example/512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a.png\",\"sha256\":\"512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a\",\"size\":15,\"media_type\":\"image/png\"},{\"url\":\"https://media.example/fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145.webp\",\"sha256\":\"fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145\",\"size\":14,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"e296ed63312d58b4e3ee160d4ca3457eb922920d3116329ca498f08e402215b0\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "update"
+ },
+ "id": "canonical_update_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"update\",\"authored_operation_id\":\"social.update.build_authored_draft\",\"event_contract_id\":\"radroots.social.update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[],\"content\":\"Carrots harvested today\"},\"expected_event_id\":\"7f2d9dbbd6d2f3db1e83338ea2e669b2458e62ded159de15a3fa98dcf9f164e3\",\"media_references\":[],\"artifact_digest\":\"9ad318496bd4a710fbc7f3e0f6d5a01de808d352db91ca56a12e710904784cc5\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "photo_update"
+ },
+ "id": "canonical_photo_update_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"photo_update\",\"authored_operation_id\":\"social.photo_update.build_authored_draft\",\"event_contract_id\":\"radroots.social.photo_update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"Strawberries at the farm stand https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"e592229776cd2a0d0e25a701f4629c0a5dc6e08481a235623d50da6ee8e8f438\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"9ed6b7a6fcec3b07bd0c9179fd5b6ecf74517390051e3c2bd1659be62a26c197\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 1,
+ "leaf": "ask"
+ },
+ "id": "canonical_ask_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"ask\",\"authored_operation_id\":\"social.ask.build_authored_draft\",\"event_contract_id\":\"radroots.social.ask.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"t\",\"radroots-ask\"],[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"When will strawberries be ready? https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"bd45fbbb5bfb8a5aa32df81d240e5f3bd56b0f8cde3a20b3f7d455801373fe48\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"41eddea199bba97adebef4ce433ebfa05d97978ce8464ed3d52762ab717c2a3c\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 31922,
+ "leaf": "event_date"
+ },
+ "id": "canonical_date_event_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_date\",\"authored_operation_id\":\"social.calendar_date_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.date_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31922,\"tags\":[[\"d\",\"farmers-market-2026\"],[\"title\",\"Moss Street Farmers Market\"],[\"start\",\"2026-07-25\"],[\"end\",\"2026-07-26\"],[\"location\",\"Victoria, BC\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"Saturday market in Victoria\"},\"expected_event_id\":\"bea82662b6acf2f1272c8335a24bb0957e35d22c2b29d3e30c094ad7398dc7c7\",\"media_references\":[{\"url\":\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\",\"sha256\":\"0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8\",\"size\":10,\"media_type\":\"image/jpeg\"}],\"artifact_digest\":\"0ec5162ab80988999aac62329d4cfc9c7efce6a82bbda539a723347d73596eb3\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 31923,
+ "leaf": "event_time"
+ },
+ "id": "canonical_time_event_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_time\",\"authored_operation_id\":\"social.calendar_time_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.time_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31923,\"tags\":[[\"d\",\"farm-tour-2026\"],[\"title\",\"Saanich Farm Tour\"],[\"start\",\"1785003600\"],[\"end\",\"1785007200\"],[\"D\",\"20659\"],[\"start_tzid\",\"America/Vancouver\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"A one-hour farm tour\"},\"expected_event_id\":\"2088ed11cd8c1495a82e6f5198ed2ee98e0cbc599f22d7f3380892155dc55894\",\"media_references\":[{\"url\":\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\",\"sha256\":\"0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8\",\"size\":10,\"media_type\":\"image/jpeg\"}],\"artifact_digest\":\"56bfb4bed87559a58922343905a73c0c96dd0a57f94ff0955caecf0589198638\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "allow",
+ "event_kind": 30402,
+ "leaf": "food_availability"
+ },
+ "id": "canonical_food_availability_is_allowed",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"food_availability\",\"authored_operation_id\":\"food_availability.build_authored_draft\",\"event_contract_id\":\"radroots.food.availability.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":30402,\"tags\":[[\"d\",\"nantes-carrots\"],[\"title\",\"Nantes Carrots\"],[\"summary\",\"Fresh bunches\"],[\"published_at\",\"1784347140\"],[\"location\",\"Central Saanich, BC\"],[\"price\",\"3\",\"CAD\"],[\"radroots:price_unit\",\"lb\"],[\"radroots:quantity\",\"24\",\"lb\"],[\"status\",\"active\"],[\"image\",\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"1200x800\"]],\"content\":\"Fresh Nantes carrots available this week.\"},\"expected_event_id\":\"76b60f1b178c0dbdc14a87de36151ea6b04a866ce50e34a0ccae2f406f514e81\",\"media_references\":[{\"url\":\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"sha256\":\"8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e\",\"size\":14,\"media_type\":\"image/png\"}],\"artifact_digest\":\"f0989cb993194af4a2f907ad8337e50378d2757c536a09afb4055a681043a7d8\"}",
+ "surface": "canonical_artifact_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.valid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_post_profile_invalid"
+ },
+ "id": "update_claim_with_ask_marker_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"update\",\"authored_operation_id\":\"social.update.build_authored_draft\",\"event_contract_id\":\"radroots.social.update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"t\",\"radroots-ask\"]],\"content\":\"Carrots harvested today\"},\"expected_event_id\":\"bce463fddb9395ba11d4b8500379a39e14afd1472b3cf47e22b6b9f2a438353e\",\"media_references\":[],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_post_profile_invalid"
+ },
+ "id": "photo_claim_with_ask_marker_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"photo_update\",\"authored_operation_id\":\"social.photo_update.build_authored_draft\",\"event_contract_id\":\"radroots.social.photo_update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"t\",\"radroots-ask\"],[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"Strawberries at the farm stand https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"57afde82467e5d72aa07ee8cf1db454945a1615295e4e112d9ffd89e987c10f4\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_post_profile_invalid"
+ },
+ "id": "ask_claim_without_marker_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"ask\",\"authored_operation_id\":\"social.ask.build_authored_draft\",\"event_contract_id\":\"radroots.social.ask.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"When will strawberries be ready? https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"3f67e9ff31c26764bc1c74ef9cafd570a7a7454b3e3768be8f8c1a6175b3d7db\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_post_profile_invalid"
+ },
+ "id": "update_claim_with_photo_profile_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"update\",\"authored_operation_id\":\"social.update.build_authored_draft\",\"event_contract_id\":\"radroots.social.update.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"imeta\",\"url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"m image/webp\",\"dim 1200x900\",\"size 13\",\"alt Fresh strawberries\",\"fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"]],\"content\":\"Strawberries at the farm stand https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\"},\"expected_event_id\":\"e592229776cd2a0d0e25a701f4629c0a5dc6e08481a235623d50da6ee8e8f438\",\"media_references\":[{\"url\":\"https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"},{\"url\":\"https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp\",\"sha256\":\"51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0\",\"size\":13,\"media_type\":\"image/webp\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_calendar_profile_invalid"
+ },
+ "id": "typed_date_event_with_generic_shape_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_date\",\"authored_operation_id\":\"social.calendar_date_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.date_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31922,\"tags\":[[\"d\",\"generic-calendar\"],[\"title\",\"Generic calendar event\"]],\"content\":\"Saturday market in Victoria\"},\"expected_event_id\":\"10e28f8f61a9bb9affa110bb6cce7cc8e371918aead30fbd31d41792db8f3325\",\"media_references\":[],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "family": "generic_calendar",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_calendar_profile_invalid"
+ },
+ "id": "typed_time_event_with_generic_shape_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"event_time\",\"authored_operation_id\":\"social.calendar_time_event.build_authored_draft\",\"event_contract_id\":\"radroots.calendar.time_event.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":31923,\"tags\":[[\"d\",\"generic-calendar\"],[\"title\",\"Generic calendar event\"]],\"content\":\"A one-hour farm tour\"},\"expected_event_id\":\"c3da57e519122044ee663cece433d56f0b5d967f8e78a1a1a80260ae7653d6bd\",\"media_references\":[],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "family": "generic_calendar",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_food_profile_invalid"
+ },
+ "id": "operational_listing_profile_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"food_availability\",\"authored_operation_id\":\"food_availability.build_authored_draft\",\"event_contract_id\":\"radroots.food.availability.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":30402,\"tags\":[[\"d\",\"nantes-carrots\"],[\"title\",\"Nantes Carrots\"],[\"summary\",\"Fresh bunches\"],[\"published_at\",\"1784347140\"],[\"location\",\"Central Saanich, BC\"],[\"price\",\"3\",\"CAD\"],[\"status\",\"active\"],[\"image\",\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"1200x800\"],[\"radroots:price\",\"3\",\"CAD\"]],\"content\":\"Fresh Nantes carrots available this week.\"},\"expected_event_id\":\"d752583639bf9edaa5ab25e491be5b565b91af5fb01167377d36fdb319dd61d7\",\"media_references\":[{\"url\":\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"sha256\":\"8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e\",\"size\":14,\"media_type\":\"image/png\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "family": "operational_listing",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_food_profile_invalid"
+ },
+ "id": "generic_nip99_profile_is_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"food_availability\",\"authored_operation_id\":\"food_availability.build_authored_draft\",\"event_contract_id\":\"radroots.food.availability.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":30402,\"tags\":[[\"d\",\"nantes-carrots\"],[\"title\",\"Nantes Carrots\"],[\"summary\",\"Fresh bunches\"],[\"published_at\",\"1784347140\"],[\"location\",\"Central Saanich, BC\"],[\"price\",\"3\",\"CAD\"],[\"status\",\"active\"],[\"image\",\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"1200x800\"]],\"content\":\"Fresh Nantes carrots available this week.\"},\"expected_event_id\":\"6ae040461fe7e6967bb48b2861bc1dbeac075f0331027e2d04b9618e2d752a2d\",\"media_references\":[{\"url\":\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"sha256\":\"8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e\",\"size\":14,\"media_type\":\"image/png\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "family": "generic_nip99",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_food_profile_invalid"
+ },
+ "id": "mixed_food_and_operational_markers_are_rejected",
+ "input": {
+ "canonical_json": "{\"schema_version\":1,\"semantic_variant\":\"food_availability\",\"authored_operation_id\":\"food_availability.build_authored_draft\",\"event_contract_id\":\"radroots.food.availability.v1\",\"expected_author\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"draft\":{\"created_at\":1784347200,\"kind\":30402,\"tags\":[[\"d\",\"nantes-carrots\"],[\"title\",\"Nantes Carrots\"],[\"summary\",\"Fresh bunches\"],[\"published_at\",\"1784347140\"],[\"location\",\"Central Saanich, BC\"],[\"price\",\"3\",\"CAD\"],[\"radroots:price_unit\",\"lb\"],[\"radroots:quantity\",\"24\",\"lb\"],[\"status\",\"active\"],[\"image\",\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"1200x800\"],[\"radroots:price\",\"3\",\"CAD\"]],\"content\":\"Fresh Nantes carrots available this week.\"},\"expected_event_id\":\"5302117c4c89252c40b57601fe8b115b779bd9256235f449ad86c50ca97c8219\",\"media_references\":[{\"url\":\"https://food.example/8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e.png\",\"sha256\":\"8a66441b557b29193781023160b0216c5518e9b84749f29ecf118a728f451a5e\",\"size\":14,\"media_type\":\"image/png\"}],\"artifact_digest\":\"0000000000000000000000000000000000000000000000000000000000000000\"}",
+ "family": "mixed_classified_listing",
+ "surface": "artifact_candidate_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "unsealed_profile_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":0,\"tags\":[],\"content\":\"{\\\"name\\\":\\\"victoria-farm\\\",\\\"display_name\\\":\\\"Victoria Farm\\\",\\\"about\\\":\\\"Seasonal produce from the Saanich Peninsula\\\",\\\"picture\\\":\\\"https://media.example/512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a.png\\\",\\\"banner\\\":\\\"https://media.example/fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145.webp\\\",\\\"nip05\\\":\\\"farm@example.com\\\",\\\"bot\\\":false}\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 0,
+ "family": "unsealed_profile",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "generic_root_note_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":1,\"tags\":[],\"content\":\"Generic root note\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 1,
+ "family": "generic_root_note",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "reply_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":1,\"tags\":[[\"e\",\"1111111111111111111111111111111111111111111111111111111111111111\",\"\",\"reply\"]],\"content\":\"Reply\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 1,
+ "family": "reply",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "comment_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":1111,\"tags\":[[\"E\",\"1111111111111111111111111111111111111111111111111111111111111111\"]],\"content\":\"Comment\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 1111,
+ "family": "comment",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "deletion_request_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":5,\"tags\":[[\"e\",\"1111111111111111111111111111111111111111111111111111111111111111\"]],\"content\":\"Removed\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 5,
+ "family": "deletion_request",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "raw_date_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":31922,\"tags\":[[\"d\",\"farmers-market-2026\"],[\"title\",\"Moss Street Farmers Market\"],[\"start\",\"2026-07-25\"],[\"end\",\"2026-07-26\"],[\"location\",\"Victoria, BC\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"Saturday market in Victoria\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 31922,
+ "family": "unsealed_calendar_event",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "raw_time_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":31923,\"tags\":[[\"d\",\"farm-tour-2026\"],[\"title\",\"Saanich Farm Tour\"],[\"start\",\"1785003600\"],[\"end\",\"1785007200\"],[\"D\",\"20659\"],[\"start_tzid\",\"America/Vancouver\"],[\"image\",\"https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg\"]],\"content\":\"A one-hour farm tour\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 31923,
+ "family": "unsealed_calendar_event",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "calendar_collection_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":31924,\"tags\":[[\"d\",\"victoria-calendar\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 31924,
+ "family": "calendar_collection",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "calendar_rsvp_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":31925,\"tags\":[[\"a\",\"31922:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:market\"]],\"content\":\"accepted\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 31925,
+ "family": "calendar_rsvp",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "bud11_authorization_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":24242,\"tags\":[[\"t\",\"upload\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 24242,
+ "family": "bud11_authorization",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "ephemeral_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":20001,\"tags\":[],\"content\":\"Ephemeral\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 20001,
+ "family": "ephemeral_event",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "trade_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":3470,\"tags\":[[\"d\",\"trade-proposal\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 3470,
+ "family": "trade",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "order_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":3422,\"tags\":[[\"d\",\"order-request\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 3422,
+ "family": "commerce_order",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "route_delivery_family_is_rejected",
+ "input": {
+ "canonical_json": "{\"product_surface\":\"route_delivery\"}",
+ "family": "route_delivery",
+ "surface": "non_event_product_surface"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "group_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":9007,\"tags\":[[\"h\",\"victoria-growers\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 9007,
+ "family": "group",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ },
+ {
+ "expected": {
+ "decision": "reject",
+ "error": "publication_allowlist_artifact_invalid",
+ "source_error": "publication_artifact_invalid_json"
+ },
+ "id": "operations_event_is_rejected",
+ "input": {
+ "canonical_json": "{\"id\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"pubkey\":\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\",\"created_at\":1784347200,\"kind\":78,\"tags\":[[\"d\",\"internal-operation\"]],\"content\":\"\",\"sig\":\"00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\"}",
+ "event_kind": 78,
+ "family": "operations",
+ "surface": "raw_nip01_event_json"
+ },
+ "kind": "publication_allowlist.allow_canonical_json.invalid"
+ }
+ ]
+}
diff --git a/crates/event_codec/tests/publication_allowlist.rs b/crates/event_codec/tests/publication_allowlist.rs
@@ -0,0 +1,224 @@
+#![cfg(feature = "serde_json")]
+
+use std::collections::BTreeSet;
+
+use radroots_event_codec::wire::publication::{
+ RadrootsPhase1PublicationArtifact,
+ allowlist::{
+ RadrootsPhase1AllowlistedPublicationArtifact, RadrootsPhase1PublicationAllowlistError,
+ RadrootsPhase1PublicationLeaf, allow_phase1_publication_artifact,
+ allow_phase1_publication_canonical_json,
+ },
+};
+use serde::Deserialize;
+
+const ALLOWLIST_VECTOR: &str = include_str!("fixtures/phase1_publication_allowlist.v1.json");
+
+#[derive(Deserialize)]
+#[serde(deny_unknown_fields)]
+struct VectorSuite {
+ suite: String,
+ contract_version: String,
+ vectors: Vec<VectorCase>,
+}
+
+#[derive(Deserialize)]
+#[serde(deny_unknown_fields)]
+struct VectorCase {
+ id: String,
+ kind: String,
+ input: VectorInput,
+ expected: VectorExpected,
+}
+
+#[derive(Deserialize)]
+#[serde(deny_unknown_fields)]
+struct VectorInput {
+ surface: String,
+ canonical_json: String,
+ family: Option<String>,
+ event_kind: Option<u32>,
+}
+
+#[derive(Deserialize)]
+#[serde(deny_unknown_fields)]
+struct VectorExpected {
+ decision: String,
+ leaf: Option<String>,
+ event_kind: Option<u32>,
+ error: Option<String>,
+ source_error: Option<String>,
+}
+
+struct Decision {
+ leaf: Option<RadrootsPhase1PublicationLeaf>,
+ event_kind: Option<u32>,
+ error: Option<&'static str>,
+ source_error: Option<String>,
+}
+
+#[test]
+fn publication_allowlist_conformance_vector_executes_every_case() {
+ let suite: VectorSuite = serde_json::from_str(ALLOWLIST_VECTOR).unwrap();
+ assert_eq!(suite.suite, "phase1_publication_allowlist");
+ assert_eq!(suite.contract_version, "1.0.0");
+ assert_eq!(suite.vectors.len(), 39);
+
+ let mut direct_allowed_leaves = Vec::new();
+ let mut canonical_allowed_leaves = Vec::new();
+ let mut rejected_families = BTreeSet::new();
+ for case in suite.vectors {
+ assert!(matches!(
+ case.kind.as_str(),
+ "publication_allowlist.allow_artifact.valid"
+ | "publication_allowlist.allow_canonical_json.valid"
+ | "publication_allowlist.allow_canonical_json.invalid"
+ ));
+ let decision = execute_case(&case.kind, &case.input);
+ match case.expected.decision.as_str() {
+ "allow" => {
+ let leaf = decision.leaf.unwrap_or_else(|| panic!("{}", case.id));
+ assert_eq!(
+ leaf.as_str(),
+ case.expected.leaf.as_deref().unwrap(),
+ "{}",
+ case.id
+ );
+ assert_eq!(decision.event_kind, case.expected.event_kind, "{}", case.id);
+ assert_eq!(decision.error, None, "{}", case.id);
+ assert_eq!(decision.source_error, None, "{}", case.id);
+ match case.kind.as_str() {
+ "publication_allowlist.allow_artifact.valid" => {
+ direct_allowed_leaves.push(leaf);
+ }
+ "publication_allowlist.allow_canonical_json.valid" => {
+ canonical_allowed_leaves.push(leaf);
+ }
+ other => panic!("{} has invalid allow case kind {other}", case.id),
+ }
+ }
+ "reject" => {
+ assert_eq!(decision.leaf, None, "{}", case.id);
+ assert_eq!(
+ decision.error,
+ case.expected.error.as_deref(),
+ "{}",
+ case.id
+ );
+ assert_eq!(
+ decision.source_error.as_deref(),
+ case.expected.source_error.as_deref(),
+ "{}",
+ case.id
+ );
+ if let Some(family) = case.input.family {
+ rejected_families.insert(family);
+ }
+ }
+ other => panic!("{} has unknown decision {other}", case.id),
+ }
+ }
+
+ let expected_leaves = vec![
+ RadrootsPhase1PublicationLeaf::Profile,
+ RadrootsPhase1PublicationLeaf::Update,
+ RadrootsPhase1PublicationLeaf::PhotoUpdate,
+ RadrootsPhase1PublicationLeaf::Ask,
+ RadrootsPhase1PublicationLeaf::EventDate,
+ RadrootsPhase1PublicationLeaf::EventTime,
+ RadrootsPhase1PublicationLeaf::FoodAvailability,
+ ];
+ direct_allowed_leaves.sort();
+ canonical_allowed_leaves.sort();
+ assert_eq!(direct_allowed_leaves, expected_leaves);
+ assert_eq!(canonical_allowed_leaves, expected_leaves);
+ assert_eq!(
+ rejected_families,
+ [
+ "bud11_authorization",
+ "calendar_collection",
+ "calendar_rsvp",
+ "commerce_order",
+ "comment",
+ "deletion_request",
+ "ephemeral_event",
+ "generic_calendar",
+ "generic_nip99",
+ "generic_root_note",
+ "group",
+ "mixed_classified_listing",
+ "operational_listing",
+ "operations",
+ "reply",
+ "route_delivery",
+ "trade",
+ "unsealed_calendar_event",
+ "unsealed_profile",
+ ]
+ .into_iter()
+ .map(str::to_owned)
+ .collect()
+ );
+}
+
+#[test]
+fn publication_allowlist_public_operation_accepts_only_the_sealed_artifact_type() {
+ type Operation = fn(
+ RadrootsPhase1PublicationArtifact,
+ ) -> Result<
+ RadrootsPhase1AllowlistedPublicationArtifact,
+ RadrootsPhase1PublicationAllowlistError,
+ >;
+ let operation: Operation = allow_phase1_publication_artifact;
+ let _ = operation;
+
+ type CanonicalOperation = fn(
+ &[u8],
+ ) -> Result<
+ RadrootsPhase1AllowlistedPublicationArtifact,
+ RadrootsPhase1PublicationAllowlistError,
+ >;
+ let canonical_operation: CanonicalOperation = allow_phase1_publication_canonical_json;
+ let _ = canonical_operation;
+}
+
+fn execute_case(case_kind: &str, input: &VectorInput) -> Decision {
+ assert!(serde_json::from_str::<serde_json::Value>(&input.canonical_json).is_ok());
+ if case_kind == "publication_allowlist.allow_artifact.valid" {
+ assert_eq!(input.surface, "sealed_artifact_json");
+ let artifact =
+ RadrootsPhase1PublicationArtifact::from_canonical_json(input.canonical_json.as_bytes())
+ .unwrap();
+ let allowed = allow_phase1_publication_artifact(artifact).unwrap();
+ return Decision {
+ leaf: Some(allowed.leaf()),
+ event_kind: Some(allowed.artifact().draft().kind()),
+ error: None,
+ source_error: None,
+ };
+ }
+ assert!(matches!(
+ (case_kind, input.surface.as_str()),
+ (
+ "publication_allowlist.allow_canonical_json.valid",
+ "canonical_artifact_json"
+ ) | (
+ "publication_allowlist.allow_canonical_json.invalid",
+ "artifact_candidate_json" | "raw_nip01_event_json" | "non_event_product_surface"
+ )
+ ));
+ match allow_phase1_publication_canonical_json(input.canonical_json.as_bytes()) {
+ Ok(allowed) => Decision {
+ leaf: Some(allowed.leaf()),
+ event_kind: Some(allowed.artifact().draft().kind()),
+ error: None,
+ source_error: None,
+ },
+ Err(error) => Decision {
+ leaf: None,
+ event_kind: input.event_kind,
+ error: Some(error.code()),
+ source_error: error.source_code().map(str::to_owned),
+ },
+ }
+}
diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs
@@ -6,6 +6,10 @@ mod comment_authority;
mod deletion_authority;
mod food_availability_projection;
mod nip09_reconciliation;
+mod phase1_publication_allowlist;
+// The former live generator remains compiled for historical unit tests and
+// immutable predecessor validation, but the allowlist successor owns writes.
+#[allow(dead_code)]
mod phase1_publication_artifact;
// The raw-source implementation remains compiled for its historical unit tests, while the
// publication successor validates its immutable artifacts instead of rebuilding current source.
@@ -20,7 +24,11 @@ pub(crate) use food_availability_projection::{
pub(crate) use nip09_reconciliation::{
validate_nip09_reconciliation_manifest, write_nip09_reconciliation_manifest,
};
+pub(crate) use phase1_publication_allowlist::{
+ validate_phase1_publication_allowlist_manifest, write_phase1_publication_allowlist_manifest,
+};
pub(crate) use phase1_publication_artifact::{
+ validate_immutable_phase1_publication_artifact_predecessor,
validate_phase1_publication_artifact_manifest, write_phase1_publication_artifact_manifest,
};
pub(crate) use registry_v7::{
@@ -58,7 +66,8 @@ pub(crate) fn validate_artifact_contracts(workspace_root: &Path) -> Result<(), S
validate_food_availability_projection_manifest(workspace_root)?;
validate_source_maintenance_manifest(workspace_root)?;
validate_raw_source_rebuild_manifest(workspace_root)?;
- validate_phase1_publication_artifact_manifest(workspace_root)?;
+ validate_immutable_phase1_publication_artifact_predecessor(workspace_root)?;
+ validate_phase1_publication_allowlist_manifest(workspace_root)?;
validate_knowledge_contract_manifest(workspace_root)
}
@@ -108,7 +117,7 @@ const REPLICA_CONTRACT_NAME: &str = "radroots_replica_contract";
const REPLICA_TRANSFER_CONSTANT: &str = "RADROOTS_REPLICA_TRANSFER_VERSION";
const REPLICA_TRANSFER_VERSION: u32 = 2;
const VENDORED_WORKSPACE_MEMBER_RELATIVE: &str = "crates/libsqlite3_sys_3_53_3";
-const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 24] = [
+const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 25] = [
(
"contracts/conformance/vectors/blossom/bud11_claims.v1.json",
"crates/blossom/tests/fixtures/bud11_claims.v1.json",
@@ -198,6 +207,10 @@ const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 24] = [
"crates/event_codec/tests/fixtures/phase1_publication_artifact.v1.json",
),
(
+ "contracts/conformance/vectors/publication/phase1_allowlist.v1.json",
+ "crates/event_codec/tests/fixtures/phase1_publication_allowlist.v1.json",
+ ),
+ (
"contracts/conformance/vectors/trade/parse_classified_listing_address.v1.json",
"crates/trade/tests/fixtures/parse_classified_listing_address.v1.json",
),
diff --git a/tools/xtask/src/contract/phase1_publication_allowlist.rs b/tools/xtask/src/contract/phase1_publication_allowlist.rs
@@ -0,0 +1,1986 @@
+use super::{
+ artifact_bundle::{GeneratedArtifact, read_regular_file, with_artifact_bundle_transaction},
+ phase1_publication_artifact::{
+ PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS, source_specs,
+ validate_immutable_phase1_publication_artifact_predecessor_under_lock,
+ },
+};
+use radroots_event_codec::wire::publication::allowlist::{
+ RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_CONTRACT_ID,
+ RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_REGISTRY_VERSION,
+ RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_VERSION,
+};
+use serde::{Deserialize, Serialize};
+use serde_json::{Value, json};
+use sha2::{Digest, Sha256};
+use std::{collections::BTreeSet, path::Path};
+
+const SCHEMA_VERSION: u32 = 1;
+const CONTRACT_ID: &str = "radroots_event_codec.phase1_publication_allowlist_v1";
+const AUTHORITY_ID: &str = "phase1_publication_allowlist_v1";
+const HASH_ALGORITHM: &str = "sha256_bytes_v1";
+const OPERATION_ID: &str = "publication_allowlist.allow_artifact";
+const CANONICAL_JSON_OPERATION_ID: &str = "publication_allowlist.allow_canonical_json";
+const WRITE_COMMAND: &str = "cargo xtask contract phase1-publication-allowlist-manifest --write";
+
+const MANIFEST_RELATIVE: &str =
+ "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.json";
+const MANIFEST_SCHEMA_RELATIVE: &str =
+ "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.schema.json";
+const MANIFEST_SHA256_RELATIVE: &str =
+ "crates/event_codec/contracts/phase1_publication_allowlist_v1.manifest.sha256";
+const GENERATED_DESCRIPTOR_RELATIVE: &str =
+ "crates/event_codec/contracts/phase1_publication_allowlist_v1.descriptor.json";
+const VECTOR_RELATIVE: &str = "contracts/conformance/vectors/publication/phase1_allowlist.v1.json";
+const VECTOR_MIRROR_RELATIVE: &str =
+ "crates/event_codec/tests/fixtures/phase1_publication_allowlist.v1.json";
+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 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";
+const REGISTRY_BYTE_LENGTH: usize = 158_021;
+const REGISTRY_SHA256: &str = "91595544310f865bdef064ee760c227c870417a95b87b3e27278f8da74fdddea";
+const REGISTRY_SIDECAR_BYTE_LENGTH: usize = 65;
+const REGISTRY_SIDECAR_SHA256: &str =
+ "823081e6f423c149865521b185a8517aea35ce4470fa77c165ad7ca5207f036b";
+const VECTOR_AUTHOR: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const VECTOR_ZERO_ID: &str = "0000000000000000000000000000000000000000000000000000000000000000";
+const VECTOR_ZERO_SIGNATURE: &str = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
+const VECTOR_CREATED_AT: u64 = 1_784_347_200;
+const VECTOR_PHOTO_IMETA_FIELDS: &[&str] = &[
+ "imeta",
+ "url https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp",
+ "x 51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0",
+ "m image/webp",
+ "dim 1200x900",
+ "size 13",
+ "alt Fresh strawberries",
+ "fallback https://backup.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp",
+];
+
+const PREDECESSOR_CONTRACT_ID: &str = "radroots_event_codec.phase1_publication_artifact_v1";
+const PREDECESSOR_ARTIFACTS: &[ImmutableArtifactSpec] = &[
+ ImmutableArtifactSpec::new(
+ "crates/event_codec/contracts/phase1_publication_artifact_v1.manifest.json",
+ 89_464,
+ "0776e1d84c9366954047e75cdf12d9acc9a7108260157c3534f22067075a385a",
+ ),
+ ImmutableArtifactSpec::new(
+ "crates/event_codec/contracts/phase1_publication_artifact_v1.manifest.schema.json",
+ 11_972,
+ "1d72cee2754e7ac45105d79b1ecf7d44251991be7a18ba106166e962000e8320",
+ ),
+ ImmutableArtifactSpec::new(
+ "crates/event_codec/contracts/phase1_publication_artifact_v1.manifest.sha256",
+ 65,
+ "586c0985b502f22241b4d90f2ecb475d43953fc3ddd66d6fbfa3b3eb9cf34444",
+ ),
+ ImmutableArtifactSpec::new(
+ "contracts/conformance/vectors/publication/phase1_artifact.v1.json",
+ 23_113,
+ "ec18c687d5b0710a48624ddb620d89157e6b645dbea8bb91c62e3a111d20c622",
+ ),
+ ImmutableArtifactSpec::new(
+ "crates/event_codec/tests/fixtures/phase1_publication_artifact.v1.json",
+ 23_113,
+ "ec18c687d5b0710a48624ddb620d89157e6b645dbea8bb91c62e3a111d20c622",
+ ),
+ ImmutableArtifactSpec::new(
+ "crates/event_codec/tests/publication_artifact.rs",
+ 34_582,
+ "7a31169eac4217a38cb3ef25eb9213f2f89e11fb17e76ceaf7449b34225e98af",
+ ),
+];
+
+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 LEAVES: &[LeafSpec] = &[
+ LeafSpec::new(
+ "profile",
+ 0,
+ "supporting",
+ "profile.build_authored_draft",
+ "radroots.profile.metadata.v1",
+ ),
+ LeafSpec::new(
+ "update",
+ 1,
+ "root",
+ "social.update.build_authored_draft",
+ "radroots.social.update.v1",
+ ),
+ LeafSpec::new(
+ "photo_update",
+ 1,
+ "root",
+ "social.photo_update.build_authored_draft",
+ "radroots.social.photo_update.v1",
+ ),
+ LeafSpec::new(
+ "ask",
+ 1,
+ "root",
+ "social.ask.build_authored_draft",
+ "radroots.social.ask.v1",
+ ),
+ LeafSpec::new(
+ "event_date",
+ 31_922,
+ "root",
+ "social.calendar_date_event.build_authored_draft",
+ "radroots.calendar.date_event.v1",
+ ),
+ LeafSpec::new(
+ "event_time",
+ 31_923,
+ "root",
+ "social.calendar_time_event.build_authored_draft",
+ "radroots.calendar.time_event.v1",
+ ),
+ LeafSpec::new(
+ "food_availability",
+ 30_402,
+ "root",
+ "food_availability.build_authored_draft",
+ "radroots.food.availability.v1",
+ ),
+];
+
+const DENIED_FAMILIES: &[&str] = &[
+ "unsealed_profile",
+ "unsealed_calendar_event",
+ "generic_root_note",
+ "reply",
+ "comment",
+ "deletion_request",
+ "generic_calendar",
+ "calendar_collection",
+ "calendar_rsvp",
+ "bud11_authorization",
+ "ephemeral_event",
+ "operational_listing",
+ "generic_nip99",
+ "mixed_classified_listing",
+ "trade",
+ "commerce_order",
+ "route_delivery",
+ "group",
+ "operations",
+];
+
+const VALID_VECTOR_SPECS: &[ValidVectorSpec] = &[
+ ValidVectorSpec::direct("sealed_profile_is_allowed", 0),
+ ValidVectorSpec::direct("sealed_update_is_allowed", 1),
+ ValidVectorSpec::direct("sealed_photo_update_is_allowed", 2),
+ ValidVectorSpec::direct("sealed_ask_is_allowed", 3),
+ ValidVectorSpec::direct("sealed_date_event_is_allowed", 4),
+ ValidVectorSpec::direct("sealed_time_event_is_allowed", 5),
+ ValidVectorSpec::direct("sealed_food_availability_is_allowed", 6),
+ ValidVectorSpec::canonical("canonical_profile_is_allowed", 0),
+ ValidVectorSpec::canonical("canonical_update_is_allowed", 1),
+ ValidVectorSpec::canonical("canonical_photo_update_is_allowed", 2),
+ ValidVectorSpec::canonical("canonical_ask_is_allowed", 3),
+ ValidVectorSpec::canonical("canonical_date_event_is_allowed", 4),
+ ValidVectorSpec::canonical("canonical_time_event_is_allowed", 5),
+ ValidVectorSpec::canonical("canonical_food_availability_is_allowed", 6),
+];
+
+const INVALID_VECTOR_SPECS: &[InvalidVectorSpec] = &[
+ InvalidVectorSpec::artifact(
+ "update_claim_with_ask_marker_is_rejected",
+ None,
+ "publication_post_profile_invalid",
+ ArtifactVectorWitness::UpdateWithAskMarker,
+ ),
+ InvalidVectorSpec::artifact(
+ "photo_claim_with_ask_marker_is_rejected",
+ None,
+ "publication_post_profile_invalid",
+ ArtifactVectorWitness::PhotoUpdateWithAskMarker,
+ ),
+ InvalidVectorSpec::artifact(
+ "ask_claim_without_marker_is_rejected",
+ None,
+ "publication_post_profile_invalid",
+ ArtifactVectorWitness::AskWithoutMarker,
+ ),
+ InvalidVectorSpec::artifact(
+ "update_claim_with_photo_profile_is_rejected",
+ None,
+ "publication_post_profile_invalid",
+ ArtifactVectorWitness::UpdateWithPhotoProfile,
+ ),
+ InvalidVectorSpec::artifact(
+ "typed_date_event_with_generic_shape_is_rejected",
+ Some("generic_calendar"),
+ "publication_calendar_profile_invalid",
+ ArtifactVectorWitness::TypedDateWithGenericShape,
+ ),
+ InvalidVectorSpec::artifact(
+ "typed_time_event_with_generic_shape_is_rejected",
+ Some("generic_calendar"),
+ "publication_calendar_profile_invalid",
+ ArtifactVectorWitness::TypedTimeWithGenericShape,
+ ),
+ InvalidVectorSpec::artifact(
+ "operational_listing_profile_is_rejected",
+ Some("operational_listing"),
+ "publication_food_profile_invalid",
+ ArtifactVectorWitness::OperationalListingProfile,
+ ),
+ InvalidVectorSpec::artifact(
+ "generic_nip99_profile_is_rejected",
+ Some("generic_nip99"),
+ "publication_food_profile_invalid",
+ ArtifactVectorWitness::GenericNip99Profile,
+ ),
+ InvalidVectorSpec::artifact(
+ "mixed_food_and_operational_markers_are_rejected",
+ Some("mixed_classified_listing"),
+ "publication_food_profile_invalid",
+ ArtifactVectorWitness::MixedFoodAndOperationalMarkers,
+ ),
+ InvalidVectorSpec::raw(
+ "unsealed_profile_is_rejected",
+ "unsealed_profile",
+ RawEventVectorWitness::Profile,
+ ),
+ InvalidVectorSpec::raw(
+ "generic_root_note_is_rejected",
+ "generic_root_note",
+ RawEventVectorWitness::GenericRootNote,
+ ),
+ InvalidVectorSpec::raw("reply_is_rejected", "reply", RawEventVectorWitness::Reply),
+ InvalidVectorSpec::raw(
+ "comment_is_rejected",
+ "comment",
+ RawEventVectorWitness::Comment,
+ ),
+ InvalidVectorSpec::raw(
+ "deletion_request_is_rejected",
+ "deletion_request",
+ RawEventVectorWitness::DeletionRequest,
+ ),
+ InvalidVectorSpec::raw(
+ "raw_date_event_is_rejected",
+ "unsealed_calendar_event",
+ RawEventVectorWitness::CalendarDate,
+ ),
+ InvalidVectorSpec::raw(
+ "raw_time_event_is_rejected",
+ "unsealed_calendar_event",
+ RawEventVectorWitness::CalendarTime,
+ ),
+ InvalidVectorSpec::raw(
+ "calendar_collection_is_rejected",
+ "calendar_collection",
+ RawEventVectorWitness::CalendarCollection,
+ ),
+ InvalidVectorSpec::raw(
+ "calendar_rsvp_is_rejected",
+ "calendar_rsvp",
+ RawEventVectorWitness::CalendarRsvp,
+ ),
+ InvalidVectorSpec::raw(
+ "bud11_authorization_is_rejected",
+ "bud11_authorization",
+ RawEventVectorWitness::Bud11Authorization,
+ ),
+ InvalidVectorSpec::raw(
+ "ephemeral_event_is_rejected",
+ "ephemeral_event",
+ RawEventVectorWitness::Ephemeral,
+ ),
+ InvalidVectorSpec::raw(
+ "trade_event_is_rejected",
+ "trade",
+ RawEventVectorWitness::Trade,
+ ),
+ InvalidVectorSpec::raw(
+ "order_event_is_rejected",
+ "commerce_order",
+ RawEventVectorWitness::CommerceOrder,
+ ),
+ InvalidVectorSpec::non_event_route_delivery(
+ "route_delivery_family_is_rejected",
+ "route_delivery",
+ ),
+ InvalidVectorSpec::raw(
+ "group_event_is_rejected",
+ "group",
+ RawEventVectorWitness::Group,
+ ),
+ InvalidVectorSpec::raw(
+ "operations_event_is_rejected",
+ "operations",
+ RawEventVectorWitness::Operations,
+ ),
+];
+
+#[derive(Clone, Copy)]
+struct ValidVectorSpec {
+ id: &'static str,
+ case_kind: &'static str,
+ surface: &'static str,
+ leaf_index: usize,
+}
+
+impl ValidVectorSpec {
+ const fn direct(id: &'static str, leaf_index: usize) -> Self {
+ Self {
+ id,
+ case_kind: "publication_allowlist.allow_artifact.valid",
+ surface: "sealed_artifact_json",
+ leaf_index,
+ }
+ }
+
+ const fn canonical(id: &'static str, leaf_index: usize) -> Self {
+ Self {
+ id,
+ case_kind: "publication_allowlist.allow_canonical_json.valid",
+ surface: "canonical_artifact_json",
+ leaf_index,
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum ArtifactVectorWitness {
+ UpdateWithAskMarker,
+ PhotoUpdateWithAskMarker,
+ AskWithoutMarker,
+ UpdateWithPhotoProfile,
+ TypedDateWithGenericShape,
+ TypedTimeWithGenericShape,
+ OperationalListingProfile,
+ GenericNip99Profile,
+ MixedFoodAndOperationalMarkers,
+}
+
+#[derive(Clone, Copy)]
+enum RawEventVectorWitness {
+ Profile,
+ GenericRootNote,
+ Reply,
+ Comment,
+ DeletionRequest,
+ CalendarDate,
+ CalendarTime,
+ CalendarCollection,
+ CalendarRsvp,
+ Bud11Authorization,
+ Ephemeral,
+ Trade,
+ CommerceOrder,
+ Group,
+ Operations,
+}
+
+impl RawEventVectorWitness {
+ const fn event_kind(self) -> u32 {
+ match self {
+ Self::Profile => 0,
+ Self::GenericRootNote | Self::Reply => 1,
+ Self::Comment => 1_111,
+ Self::DeletionRequest => 5,
+ Self::CalendarDate => 31_922,
+ Self::CalendarTime => 31_923,
+ Self::CalendarCollection => 31_924,
+ Self::CalendarRsvp => 31_925,
+ Self::Bud11Authorization => 24_242,
+ Self::Ephemeral => 20_001,
+ Self::Trade => 3_470,
+ Self::CommerceOrder => 3_422,
+ Self::Group => 9_007,
+ Self::Operations => 78,
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+enum InvalidVectorWitness {
+ Artifact(ArtifactVectorWitness),
+ RawEvent(RawEventVectorWitness),
+ NonEventRouteDelivery,
+}
+
+#[derive(Clone, Copy)]
+struct InvalidVectorSpec {
+ id: &'static str,
+ family: Option<&'static str>,
+ source_error: &'static str,
+ witness: InvalidVectorWitness,
+}
+
+impl InvalidVectorSpec {
+ const fn artifact(
+ id: &'static str,
+ family: Option<&'static str>,
+ source_error: &'static str,
+ witness: ArtifactVectorWitness,
+ ) -> Self {
+ Self {
+ id,
+ family,
+ source_error,
+ witness: InvalidVectorWitness::Artifact(witness),
+ }
+ }
+
+ const fn raw(id: &'static str, family: &'static str, witness: RawEventVectorWitness) -> Self {
+ Self {
+ id,
+ family: Some(family),
+ source_error: "publication_artifact_invalid_json",
+ witness: InvalidVectorWitness::RawEvent(witness),
+ }
+ }
+
+ const fn non_event_route_delivery(id: &'static str, family: &'static str) -> Self {
+ Self {
+ id,
+ family: Some(family),
+ source_error: "publication_artifact_invalid_json",
+ witness: InvalidVectorWitness::NonEventRouteDelivery,
+ }
+ }
+
+ const fn surface(self) -> &'static str {
+ match self.witness {
+ InvalidVectorWitness::Artifact(_) => "artifact_candidate_json",
+ InvalidVectorWitness::RawEvent(_) => "raw_nip01_event_json",
+ InvalidVectorWitness::NonEventRouteDelivery => "non_event_product_surface",
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+struct ImmutableArtifactSpec {
+ relative: &'static str,
+ byte_length: usize,
+ sha256: &'static str,
+}
+
+impl ImmutableArtifactSpec {
+ const fn new(relative: &'static str, byte_length: usize, sha256: &'static str) -> Self {
+ Self {
+ relative,
+ byte_length,
+ sha256,
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+struct LeafSpec {
+ leaf: &'static str,
+ kind: u32,
+ publication_role: &'static str,
+ authored_operation_id: &'static str,
+ event_contract_id: &'static str,
+}
+
+impl LeafSpec {
+ const fn new(
+ leaf: &'static str,
+ kind: u32,
+ publication_role: &'static str,
+ authored_operation_id: &'static str,
+ event_contract_id: &'static str,
+ ) -> Self {
+ Self {
+ leaf,
+ kind,
+ publication_role,
+ authored_operation_id,
+ event_contract_id,
+ }
+ }
+}
+
+#[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 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>,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct RegistryDescriptor {
+ version: u32,
+ inventory: FileDescriptor,
+ sidecar: FileDescriptor,
+ evolution: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct LeafDescriptor {
+ leaf: String,
+ kind: u32,
+ publication_role: String,
+ authored_operation_id: String,
+ event_contract_id: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct AllowlistDescriptor {
+ version: u32,
+ contract_id: String,
+ operation_id: String,
+ canonical_json_operation_id: String,
+ input_type: String,
+ output_type: String,
+ allowed_leaves: Vec<LeafDescriptor>,
+ kind_one_precedence: Vec<String>,
+ event_policy: String,
+ classified_listing_policy: String,
+ denied_families: Vec<String>,
+ granted_capability: String,
+ excluded_capabilities: Vec<String>,
+}
+
+#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(deny_unknown_fields)]
+struct ResultVectorDescriptor {
+ canonical_path: String,
+ mirror_path: String,
+ byte_length: u64,
+ sha256: String,
+ hash_algorithm: String,
+ executor_path: String,
+ executor_test: String,
+ valid_case_ids: Vec<String>,
+ invalid_case_ids: Vec<String>,
+}
+
+#[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,
+ authority_id: String,
+ manifest_schema: FileDescriptor,
+ predecessor: PredecessorDescriptor,
+ event_contract_registry: RegistryDescriptor,
+ allowlist: AllowlistDescriptor,
+ predecessor_source_supersessions: Vec<String>,
+ source_files: Vec<SourceFileDescriptor>,
+ result_vector: ResultVectorDescriptor,
+ release: ReleaseDescriptor,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct VectorSuite {
+ suite: String,
+ contract_version: String,
+ vectors: Vec<VectorCase>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct VectorCase {
+ id: String,
+ kind: String,
+ input: Value,
+ expected: Value,
+}
+
+struct ValidatedVector {
+ bytes: Vec<u8>,
+ valid_case_ids: Vec<String>,
+ invalid_case_ids: Vec<String>,
+}
+
+pub(crate) fn write_phase1_publication_allowlist_manifest(
+ workspace_root: &Path,
+) -> Result<(), String> {
+ with_artifact_bundle_transaction(workspace_root, |transaction| {
+ transaction.write(expected_artifacts(workspace_root)?)?;
+ validate_manifest_under_lock(workspace_root)
+ })
+}
+
+pub(crate) fn validate_phase1_publication_allowlist_manifest(
+ workspace_root: &Path,
+) -> Result<(), String> {
+ with_artifact_bundle_transaction(workspace_root, |_| {
+ validate_manifest_under_lock(workspace_root)
+ })
+}
+
+fn validate_manifest_under_lock(workspace_root: &Path) -> Result<(), String> {
+ validate_immutable_phase1_publication_artifact_predecessor_under_lock(workspace_root)?;
+ for artifact in expected_artifacts(workspace_root)? {
+ let actual = read_regular_file(workspace_root, artifact.relative)?;
+ if actual != artifact.contents {
+ return Err(format!(
+ "generated Phase 1 publication allowlist contract {} is stale; run {WRITE_COMMAND}",
+ artifact.relative
+ ));
+ }
+ }
+
+ let bytes = read_regular_file(workspace_root, MANIFEST_RELATIVE)?;
+ let manifest: AllowlistManifest = serde_json::from_slice(&bytes)
+ .map_err(|error| format!("parse {MANIFEST_RELATIVE}: {error}"))?;
+ validate_canonical_json(MANIFEST_RELATIVE, &bytes, &manifest)?;
+ validate_manifest_shape(&manifest)?;
+
+ let schema_bytes = read_regular_file(workspace_root, MANIFEST_SCHEMA_RELATIVE)?;
+ let schema: Value = serde_json::from_slice(&schema_bytes)
+ .map_err(|error| format!("parse {MANIFEST_SCHEMA_RELATIVE}: {error}"))?;
+ validate_canonical_json(MANIFEST_SCHEMA_RELATIVE, &schema_bytes, &schema)?;
+ let instance = serde_json::to_value(&manifest)
+ .map_err(|error| format!("serialize {MANIFEST_RELATIVE}: {error}"))?;
+ validate_json_schema(&schema, &instance)?;
+
+ let sidecar = read_regular_file(workspace_root, MANIFEST_SHA256_RELATIVE)?;
+ if sidecar != format!("{}\n", sha256_hex(&bytes)).as_bytes() {
+ return Err(format!(
+ "{MANIFEST_SHA256_RELATIVE} must authenticate the exact allowlist manifest bytes"
+ ));
+ }
+ Ok(())
+}
+
+fn expected_artifacts(workspace_root: &Path) -> Result<Vec<GeneratedArtifact>, String> {
+ validate_immutable_phase1_publication_artifact_predecessor_under_lock(workspace_root)?;
+ validate_source_contract(workspace_root)?;
+ let schema_bytes = canonical_json_bytes(&manifest_schema())?;
+ let manifest = describe_manifest(workspace_root, &schema_bytes)?;
+ let manifest_bytes = canonical_json_bytes(&manifest)?;
+ let sidecar_bytes = format!("{}\n", sha256_hex(&manifest_bytes)).into_bytes();
+ let descriptor_bytes = canonical_json_bytes(&json!({
+ "schema_version": SCHEMA_VERSION,
+ "contract_id": CONTRACT_ID,
+ "manifest": descriptor_for_bytes(MANIFEST_RELATIVE, &manifest_bytes)?,
+ "manifest_schema": descriptor_for_bytes(MANIFEST_SCHEMA_RELATIVE, &schema_bytes)?,
+ "manifest_sidecar": descriptor_for_bytes(MANIFEST_SHA256_RELATIVE, &sidecar_bytes)?,
+ "predecessor_manifest_sha256": PREDECESSOR_ARTIFACTS[0].sha256,
+ "event_contract_registry_v7_sha256": REGISTRY_SHA256,
+ }))?;
+ let vector_bytes = read_regular_file(workspace_root, VECTOR_RELATIVE)?;
+ Ok(vec![
+ GeneratedArtifact {
+ relative: MANIFEST_RELATIVE,
+ contents: manifest_bytes,
+ },
+ GeneratedArtifact {
+ relative: MANIFEST_SCHEMA_RELATIVE,
+ contents: schema_bytes,
+ },
+ GeneratedArtifact {
+ relative: MANIFEST_SHA256_RELATIVE,
+ contents: sidecar_bytes,
+ },
+ GeneratedArtifact {
+ relative: GENERATED_DESCRIPTOR_RELATIVE,
+ contents: descriptor_bytes,
+ },
+ GeneratedArtifact {
+ relative: VECTOR_MIRROR_RELATIVE,
+ contents: vector_bytes,
+ },
+ ])
+}
+
+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,
+ contract_id: CONTRACT_ID.to_owned(),
+ authority_id: AUTHORITY_ID.to_owned(),
+ manifest_schema: descriptor_for_bytes(MANIFEST_SCHEMA_RELATIVE, schema_bytes)?,
+ predecessor: PredecessorDescriptor {
+ contract_id: PREDECESSOR_CONTRACT_ID.to_owned(),
+ immutable_artifacts: PREDECESSOR_ARTIFACTS
+ .iter()
+ .map(|spec| FileDescriptor {
+ path: spec.relative.to_owned(),
+ byte_length: spec.byte_length as u64,
+ sha256: spec.sha256.to_owned(),
+ hash_algorithm: HASH_ALGORITHM.to_owned(),
+ })
+ .collect(),
+ },
+ event_contract_registry: RegistryDescriptor {
+ version: RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_REGISTRY_VERSION,
+ inventory: descriptor_for_file(workspace_root, REGISTRY_RELATIVE)?,
+ sidecar: descriptor_for_file(workspace_root, REGISTRY_SIDECAR_RELATIVE)?,
+ evolution: "immutable_registry_v7_plus_additive_phase1_publication_allowlist_v1"
+ .to_owned(),
+ },
+ 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_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_allowlist_descriptor() -> AllowlistDescriptor {
+ AllowlistDescriptor {
+ version: RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_VERSION,
+ contract_id: RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_CONTRACT_ID.to_owned(),
+ operation_id: OPERATION_ID.to_owned(),
+ canonical_json_operation_id: CANONICAL_JSON_OPERATION_ID.to_owned(),
+ input_type: "RadrootsPhase1PublicationArtifact".to_owned(),
+ output_type: "RadrootsPhase1AllowlistedPublicationArtifact".to_owned(),
+ allowed_leaves: LEAVES
+ .iter()
+ .map(|leaf| LeafDescriptor {
+ leaf: leaf.leaf.to_owned(),
+ kind: leaf.kind,
+ publication_role: leaf.publication_role.to_owned(),
+ authored_operation_id: leaf.authored_operation_id.to_owned(),
+ event_contract_id: leaf.event_contract_id.to_owned(),
+ })
+ .collect(),
+ kind_one_precedence: ["ask", "photo_update", "update"]
+ .into_iter()
+ .map(str::to_owned)
+ .collect(),
+ 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(),
+ denied_families: DENIED_FAMILIES
+ .iter()
+ .map(|family| (*family).to_owned())
+ .collect(),
+ granted_capability: "phase1_durable_publication_lane_entry_only_v1".to_owned(),
+ excluded_capabilities: [
+ "signing",
+ "media_upload",
+ "media_retrieval",
+ "media_readiness",
+ "relay_transport",
+ "product_entitlement",
+ "inbound_registry_admission",
+ ]
+ .into_iter()
+ .map(str::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_vector(workspace_root)?;
+
+ let paths = successor_source_specs(workspace_root)?
+ .into_iter()
+ .map(|(_, path)| path)
+ .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}"
+ ));
+ }
+ }
+ for path in GENERATED_ARTIFACT_PATHS {
+ if paths.contains(*path) {
+ return Err(format!(
+ "allowlist source inventory recursively includes generated artifact {path}"
+ ));
+ }
+ }
+ Ok(())
+}
+
+fn validate_registry_identity(workspace_root: &Path) -> Result<(), String> {
+ for (path, expected_len, expected_sha) in [
+ (REGISTRY_RELATIVE, REGISTRY_BYTE_LENGTH, REGISTRY_SHA256),
+ (
+ REGISTRY_SIDECAR_RELATIVE,
+ REGISTRY_SIDECAR_BYTE_LENGTH,
+ REGISTRY_SIDECAR_SHA256,
+ ),
+ ] {
+ let bytes = read_regular_file(workspace_root, path)?;
+ if bytes.len() != expected_len || sha256_hex(&bytes) != expected_sha {
+ return Err(format!(
+ "immutable event-contract registry-v7 artifact {path} drifted"
+ ));
+ }
+ }
+ let inventory = read_regular_file(workspace_root, REGISTRY_RELATIVE)?;
+ let value: Value = serde_json::from_slice(&inventory)
+ .map_err(|error| format!("parse {REGISTRY_RELATIVE}: {error}"))?;
+ if value
+ .get("event_contract_registry_version")
+ .and_then(Value::as_u64)
+ != Some(7)
+ {
+ return Err("event-contract registry-v7 identity drifted".to_owned());
+ }
+ let sidecar = read_regular_file(workspace_root, REGISTRY_SIDECAR_RELATIVE)?;
+ if sidecar != format!("{}\n", sha256_hex(&inventory)).as_bytes() {
+ return Err(
+ "event-contract registry-v7 sidecar does not authenticate inventory".to_owned(),
+ );
+ }
+ Ok(())
+}
+
+fn validate_operations_authority(workspace_root: &Path) -> Result<(), String> {
+ let manifest = parse_toml(workspace_root, OPERATIONS_RELATIVE)?;
+ let public_types = toml_string_array(
+ "shared_types.public",
+ manifest
+ .get("shared_types")
+ .and_then(|value| value.get("public")),
+ )?;
+ for required in PUBLIC_TYPES {
+ if public_types
+ .iter()
+ .filter(|candidate| candidate.as_str() == *required)
+ .count()
+ != 1
+ {
+ return Err(format!(
+ "shared public types must contain {required} exactly once"
+ ));
+ }
+ }
+ let operations = manifest
+ .get("operations")
+ .and_then(toml::Value::as_table)
+ .ok_or_else(|| "operations.toml has no operations table".to_owned())?;
+ for (key, operation_id, input) in [
+ (
+ "phase1_publication_allowlist_allow_artifact",
+ OPERATION_ID,
+ "RadrootsPhase1PublicationArtifact",
+ ),
+ (
+ "phase1_publication_allowlist_allow_canonical_json",
+ CANONICAL_JSON_OPERATION_ID,
+ "Bytes",
+ ),
+ ] {
+ let operation = operations
+ .get(key)
+ .and_then(toml::Value::as_table)
+ .ok_or_else(|| format!("operations.toml is missing {key}"))?;
+ let case_kinds: &[&str] = if operation_id == OPERATION_ID {
+ &["publication_allowlist.allow_artifact.valid"]
+ } else {
+ &[
+ "publication_allowlist.allow_canonical_json.valid",
+ "publication_allowlist.allow_canonical_json.invalid",
+ ]
+ };
+ validate_allowlist_operation(operation, operation_id, input, case_kinds)?;
+ }
+ Ok(())
+}
+
+fn validate_allowlist_operation(
+ operation: &toml::map::Map<String, toml::Value>,
+ operation_id: &str,
+ input: &str,
+ case_kinds: &[&str],
+) -> Result<(), String> {
+ for (field, expected) in [
+ ("domain", "publication"),
+ ("id", operation_id),
+ ("stability", "beta"),
+ ("error_class", "validation_error"),
+ ("signing", "none"),
+ ("transport", "none"),
+ ] {
+ if operation.get(field).and_then(toml::Value::as_str) != Some(expected) {
+ return Err(format!(
+ "publication allowlist operation {field} must be {expected}"
+ ));
+ }
+ }
+ if operation
+ .get("deterministic")
+ .and_then(toml::Value::as_bool)
+ != Some(true)
+ || toml_string_array("allowlist inputs", operation.get("inputs"))? != vec![input.to_owned()]
+ || toml_string_array("allowlist outputs", operation.get("outputs"))?
+ != vec!["RadrootsPhase1AllowlistedPublicationArtifact".to_owned()]
+ {
+ return Err("publication allowlist signature or determinism drifted".to_owned());
+ }
+ let implementation = operation
+ .get("implementation")
+ .and_then(toml::Value::as_table)
+ .ok_or_else(|| "publication allowlist implementation is missing".to_owned())?;
+ let modules = toml_string_array(
+ "allowlist implementation modules",
+ implementation.get("rust_modules"),
+ )?;
+ for required in [
+ "crates/event_codec/src/wire/publication.rs",
+ "crates/event_codec/src/wire/publication/allowlist.rs",
+ "crates/event_codec/src/post/inbound/registry_v7.rs",
+ "crates/event_codec/src/food_availability/inbound/registry_v7.rs",
+ ] {
+ if !modules.iter().any(|module| module == required) {
+ return Err(format!(
+ "publication allowlist implementation is missing {required}"
+ ));
+ }
+ }
+ let conformance = operation
+ .get("conformance")
+ .and_then(toml::Value::as_table)
+ .ok_or_else(|| "publication allowlist conformance is missing".to_owned())?;
+ if conformance.get("vector").and_then(toml::Value::as_str) != Some(VECTOR_RELATIVE)
+ || toml_string_array("allowlist case kinds", conformance.get("case_kinds"))?
+ != case_kinds
+ .iter()
+ .map(|case_kind| (*case_kind).to_owned())
+ .collect::<Vec<_>>()
+ {
+ return Err("publication allowlist conformance authority drifted".to_owned());
+ }
+ 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)
+ .map_err(|error| format!("parse {VECTOR_RELATIVE}: {error}"))?;
+ let canonical = canonical_json_bytes(
+ &serde_json::from_slice::<Value>(&bytes)
+ .map_err(|error| format!("parse {VECTOR_RELATIVE} as JSON value: {error}"))?,
+ )?;
+ if canonical != bytes {
+ return Err(format!("{VECTOR_RELATIVE} must be canonical pretty JSON"));
+ }
+ if suite.suite != "phase1_publication_allowlist" || suite.contract_version != "1.0.0" {
+ return Err(format!("{VECTOR_RELATIVE} identity drifted"));
+ }
+ let expected_ids = VALID_VECTOR_SPECS
+ .iter()
+ .map(|spec| spec.id)
+ .chain(INVALID_VECTOR_SPECS.iter().map(|spec| spec.id))
+ .collect::<Vec<_>>();
+ let actual_ids = suite
+ .vectors
+ .iter()
+ .map(|vector| vector.id.as_str())
+ .collect::<Vec<_>>();
+ if actual_ids != expected_ids {
+ return Err(format!("{VECTOR_RELATIVE} case inventory or order drifted"));
+ }
+ if actual_ids.iter().copied().collect::<BTreeSet<_>>().len() != actual_ids.len() {
+ return Err(format!("{VECTOR_RELATIVE} case ids must be unique"));
+ }
+ let mut denied_families = BTreeSet::new();
+ for (index, vector) in suite.vectors.iter().enumerate() {
+ let input = vector
+ .input
+ .as_object()
+ .ok_or_else(|| format!("{} input must be an object", vector.id))?;
+ let expected = vector
+ .expected
+ .as_object()
+ .ok_or_else(|| format!("{} expected must be an object", vector.id))?;
+ let canonical_json = input
+ .get("canonical_json")
+ .and_then(Value::as_str)
+ .ok_or_else(|| format!("{} must carry exact canonical_json", vector.id))?;
+ let candidate: Value = serde_json::from_str(canonical_json)
+ .map_err(|error| format!("{} canonical_json is invalid: {error}", vector.id))?;
+ if let Some(spec) = VALID_VECTOR_SPECS.get(index).copied() {
+ let leaf = LEAVES[spec.leaf_index];
+ let input_keys = input.keys().map(String::as_str).collect::<BTreeSet<_>>();
+ let expected_input_keys = ["canonical_json", "surface"]
+ .into_iter()
+ .collect::<BTreeSet<_>>();
+ if input.len() != 2
+ || input_keys != expected_input_keys
+ || vector.kind != spec.case_kind
+ || input.get("surface").and_then(Value::as_str) != Some(spec.surface)
+ || expected.len() != 3
+ || expected.get("decision").and_then(Value::as_str) != Some("allow")
+ || expected.get("leaf").and_then(Value::as_str) != Some(leaf.leaf)
+ || expected.get("event_kind").and_then(Value::as_u64) != Some(u64::from(leaf.kind))
+ || candidate.get("semantic_variant").and_then(Value::as_str) != Some(leaf.leaf)
+ || candidate
+ .get("authored_operation_id")
+ .and_then(Value::as_str)
+ != Some(leaf.authored_operation_id)
+ || candidate.get("event_contract_id").and_then(Value::as_str)
+ != Some(leaf.event_contract_id)
+ || candidate.pointer("/draft/kind").and_then(Value::as_u64)
+ != Some(u64::from(leaf.kind))
+ {
+ return Err(format!("{} allowed leaf descriptor drifted", vector.id));
+ }
+ validate_artifact_envelope_keys(&vector.id, &candidate)?;
+ if spec.case_kind == "publication_allowlist.allow_canonical_json.valid"
+ && canonical_json
+ != suite.vectors[spec.leaf_index]
+ .input
+ .get("canonical_json")
+ .and_then(Value::as_str)
+ .ok_or_else(|| {
+ format!("{} direct-operation counterpart is missing", vector.id)
+ })?
+ {
+ return Err(format!(
+ "{} must reuse the exact direct-operation artifact bytes",
+ vector.id
+ ));
+ }
+ } else {
+ let spec = INVALID_VECTOR_SPECS[index - VALID_VECTOR_SPECS.len()];
+ let family = input.get("family").and_then(Value::as_str);
+ if vector.kind != "publication_allowlist.allow_canonical_json.invalid"
+ || input.get("surface").and_then(Value::as_str) != Some(spec.surface())
+ || family != spec.family
+ || expected.len() != 3
+ || expected.get("decision").and_then(Value::as_str) != Some("reject")
+ || expected.get("error").and_then(Value::as_str)
+ != Some("publication_allowlist_artifact_invalid")
+ || expected.get("source_error").and_then(Value::as_str) != Some(spec.source_error)
+ {
+ return Err(format!("{} rejection authority drifted", vector.id));
+ }
+ if let Some(family) = family {
+ denied_families.insert(family);
+ }
+ validate_invalid_vector_evidence(&vector.id, spec, input, &candidate)?;
+ }
+ }
+ let expected_denied = DENIED_FAMILIES.iter().copied().collect::<BTreeSet<_>>();
+ if denied_families != expected_denied {
+ return Err(format!(
+ "{VECTOR_RELATIVE} denied-family evidence does not match the declared taxonomy"
+ ));
+ }
+ Ok(ValidatedVector {
+ bytes,
+ valid_case_ids: VALID_VECTOR_SPECS
+ .iter()
+ .map(|spec| spec.id.to_owned())
+ .collect(),
+ invalid_case_ids: INVALID_VECTOR_SPECS
+ .iter()
+ .map(|spec| spec.id.to_owned())
+ .collect(),
+ })
+}
+
+fn validate_invalid_vector_evidence(
+ case_id: &str,
+ spec: InvalidVectorSpec,
+ input: &serde_json::Map<String, Value>,
+ candidate: &Value,
+) -> Result<(), String> {
+ match spec.witness {
+ InvalidVectorWitness::Artifact(witness) => {
+ let expected_input_len = 2 + usize::from(spec.family.is_some());
+ if input.len() != expected_input_len || input.get("event_kind").is_some() {
+ return Err(format!("{case_id} artifact input evidence drifted"));
+ }
+ validate_artifact_envelope_keys(case_id, candidate)?;
+ validate_artifact_vector_witness(case_id, witness, candidate)
+ }
+ InvalidVectorWitness::RawEvent(witness) => {
+ let event_kind = input
+ .get("event_kind")
+ .and_then(Value::as_u64)
+ .ok_or_else(|| format!("{case_id} raw event kind is missing"))?;
+ if input.len() != 4 || event_kind != u64::from(witness.event_kind()) {
+ return Err(format!("{case_id} raw event input evidence drifted"));
+ }
+ validate_raw_event_vector_witness(case_id, witness, candidate)
+ }
+ InvalidVectorWitness::NonEventRouteDelivery => {
+ let input_keys = input.keys().map(String::as_str).collect::<BTreeSet<_>>();
+ let expected_input_keys = ["canonical_json", "family", "surface"]
+ .into_iter()
+ .collect::<BTreeSet<_>>();
+ if input.len() != 3
+ || input_keys != expected_input_keys
+ || candidate != &json!({"product_surface": "route_delivery"})
+ {
+ return Err(format!(
+ "{case_id} must witness route/delivery as an exact non-event product surface"
+ ));
+ }
+ Ok(())
+ }
+ }
+}
+
+fn validate_artifact_envelope_keys(case_id: &str, candidate: &Value) -> Result<(), String> {
+ let candidate = candidate
+ .as_object()
+ .ok_or_else(|| format!("{case_id} artifact candidate must be an object"))?;
+ let actual_keys = candidate
+ .keys()
+ .map(String::as_str)
+ .collect::<BTreeSet<_>>();
+ let expected_keys = [
+ "schema_version",
+ "semantic_variant",
+ "authored_operation_id",
+ "event_contract_id",
+ "expected_author",
+ "draft",
+ "expected_event_id",
+ "media_references",
+ "artifact_digest",
+ ]
+ .into_iter()
+ .collect::<BTreeSet<_>>();
+ if actual_keys != expected_keys {
+ return Err(format!("{case_id} artifact envelope evidence drifted"));
+ }
+ Ok(())
+}
+
+struct ArtifactWitnessDescriptor {
+ semantic_variant: &'static str,
+ authored_operation_id: &'static str,
+ event_contract_id: &'static str,
+ event_kind: u32,
+ tag_names: &'static [&'static str],
+ content: &'static str,
+ media_reference_count: usize,
+}
+
+fn artifact_witness_descriptor(witness: ArtifactVectorWitness) -> ArtifactWitnessDescriptor {
+ match witness {
+ ArtifactVectorWitness::UpdateWithAskMarker => ArtifactWitnessDescriptor {
+ semantic_variant: "update",
+ authored_operation_id: "social.update.build_authored_draft",
+ event_contract_id: "radroots.social.update.v1",
+ event_kind: 1,
+ tag_names: &["t"],
+ content: "Carrots harvested today",
+ media_reference_count: 0,
+ },
+ ArtifactVectorWitness::PhotoUpdateWithAskMarker => ArtifactWitnessDescriptor {
+ semantic_variant: "photo_update",
+ authored_operation_id: "social.photo_update.build_authored_draft",
+ event_contract_id: "radroots.social.photo_update.v1",
+ event_kind: 1,
+ tag_names: &["t", "imeta"],
+ content: "Strawberries at the farm stand https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp",
+ media_reference_count: 2,
+ },
+ ArtifactVectorWitness::AskWithoutMarker => ArtifactWitnessDescriptor {
+ semantic_variant: "ask",
+ authored_operation_id: "social.ask.build_authored_draft",
+ event_contract_id: "radroots.social.ask.v1",
+ event_kind: 1,
+ tag_names: &["imeta"],
+ content: "When will strawberries be ready? https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp",
+ media_reference_count: 2,
+ },
+ ArtifactVectorWitness::UpdateWithPhotoProfile => ArtifactWitnessDescriptor {
+ semantic_variant: "update",
+ authored_operation_id: "social.update.build_authored_draft",
+ event_contract_id: "radroots.social.update.v1",
+ event_kind: 1,
+ tag_names: &["imeta"],
+ content: "Strawberries at the farm stand https://media.example/51bcf96cda2475475246e3a994aabfee7ff9d7694ba0db9bdc74408632827ad0.webp",
+ media_reference_count: 2,
+ },
+ ArtifactVectorWitness::TypedDateWithGenericShape => ArtifactWitnessDescriptor {
+ semantic_variant: "event_date",
+ authored_operation_id: "social.calendar_date_event.build_authored_draft",
+ event_contract_id: "radroots.calendar.date_event.v1",
+ event_kind: 31_922,
+ tag_names: &["d", "title"],
+ content: "Saturday market in Victoria",
+ media_reference_count: 0,
+ },
+ ArtifactVectorWitness::TypedTimeWithGenericShape => ArtifactWitnessDescriptor {
+ semantic_variant: "event_time",
+ authored_operation_id: "social.calendar_time_event.build_authored_draft",
+ event_contract_id: "radroots.calendar.time_event.v1",
+ event_kind: 31_923,
+ tag_names: &["d", "title"],
+ content: "A one-hour farm tour",
+ media_reference_count: 0,
+ },
+ ArtifactVectorWitness::OperationalListingProfile => ArtifactWitnessDescriptor {
+ semantic_variant: "food_availability",
+ authored_operation_id: "food_availability.build_authored_draft",
+ event_contract_id: "radroots.food.availability.v1",
+ event_kind: 30_402,
+ tag_names: &[
+ "d",
+ "title",
+ "summary",
+ "published_at",
+ "location",
+ "price",
+ "status",
+ "image",
+ "radroots:price",
+ ],
+ content: "Fresh Nantes carrots available this week.",
+ media_reference_count: 1,
+ },
+ ArtifactVectorWitness::GenericNip99Profile => ArtifactWitnessDescriptor {
+ semantic_variant: "food_availability",
+ authored_operation_id: "food_availability.build_authored_draft",
+ event_contract_id: "radroots.food.availability.v1",
+ event_kind: 30_402,
+ tag_names: &[
+ "d",
+ "title",
+ "summary",
+ "published_at",
+ "location",
+ "price",
+ "status",
+ "image",
+ ],
+ content: "Fresh Nantes carrots available this week.",
+ media_reference_count: 1,
+ },
+ ArtifactVectorWitness::MixedFoodAndOperationalMarkers => ArtifactWitnessDescriptor {
+ semantic_variant: "food_availability",
+ authored_operation_id: "food_availability.build_authored_draft",
+ event_contract_id: "radroots.food.availability.v1",
+ event_kind: 30_402,
+ tag_names: &[
+ "d",
+ "title",
+ "summary",
+ "published_at",
+ "location",
+ "price",
+ "radroots:price_unit",
+ "radroots:quantity",
+ "status",
+ "image",
+ "radroots:price",
+ ],
+ content: "Fresh Nantes carrots available this week.",
+ media_reference_count: 1,
+ },
+ }
+}
+
+fn validate_artifact_vector_witness(
+ case_id: &str,
+ witness: ArtifactVectorWitness,
+ candidate: &Value,
+) -> Result<(), String> {
+ let descriptor = artifact_witness_descriptor(witness);
+ let tags = candidate
+ .pointer("/draft/tags")
+ .and_then(Value::as_array)
+ .ok_or_else(|| format!("{case_id} artifact tags are missing"))?;
+ let media_references = candidate
+ .get("media_references")
+ .and_then(Value::as_array)
+ .ok_or_else(|| format!("{case_id} media references are missing"))?;
+ if candidate.get("schema_version").and_then(Value::as_u64) != Some(1)
+ || candidate.get("semantic_variant").and_then(Value::as_str)
+ != Some(descriptor.semantic_variant)
+ || candidate
+ .get("authored_operation_id")
+ .and_then(Value::as_str)
+ != Some(descriptor.authored_operation_id)
+ || candidate.get("event_contract_id").and_then(Value::as_str)
+ != Some(descriptor.event_contract_id)
+ || candidate.get("expected_author").and_then(Value::as_str) != Some(VECTOR_AUTHOR)
+ || candidate.pointer("/draft/kind").and_then(Value::as_u64)
+ != Some(u64::from(descriptor.event_kind))
+ || candidate
+ .pointer("/draft/created_at")
+ .and_then(Value::as_u64)
+ != Some(VECTOR_CREATED_AT)
+ || candidate.pointer("/draft/content").and_then(Value::as_str) != Some(descriptor.content)
+ || media_references.len() != descriptor.media_reference_count
+ || candidate.get("artifact_digest").and_then(Value::as_str) != Some(VECTOR_ZERO_ID)
+ {
+ return Err(format!("{case_id} claimed artifact profile drifted"));
+ }
+ validate_exact_tag_names(case_id, tags, descriptor.tag_names)?;
+
+ match witness {
+ ArtifactVectorWitness::UpdateWithAskMarker => {
+ require_exact_tag(case_id, tags, &["t", "radroots-ask"])?;
+ }
+ ArtifactVectorWitness::PhotoUpdateWithAskMarker => {
+ require_exact_tag(case_id, tags, &["t", "radroots-ask"])?;
+ require_exact_tag(case_id, tags, VECTOR_PHOTO_IMETA_FIELDS)?;
+ }
+ ArtifactVectorWitness::AskWithoutMarker | ArtifactVectorWitness::UpdateWithPhotoProfile => {
+ forbid_tag_name(case_id, tags, "t")?;
+ require_exact_tag(case_id, tags, VECTOR_PHOTO_IMETA_FIELDS)?;
+ }
+ ArtifactVectorWitness::TypedDateWithGenericShape
+ | ArtifactVectorWitness::TypedTimeWithGenericShape => {
+ require_exact_tag(case_id, tags, &["d", "generic-calendar"])?;
+ require_exact_tag(case_id, tags, &["title", "Generic calendar event"])?;
+ }
+ ArtifactVectorWitness::OperationalListingProfile => {
+ require_exact_tag(case_id, tags, &["radroots:price", "3", "CAD"])?;
+ forbid_tag_name(case_id, tags, "radroots:price_unit")?;
+ forbid_tag_name(case_id, tags, "radroots:quantity")?;
+ }
+ ArtifactVectorWitness::GenericNip99Profile => {
+ forbid_tag_name(case_id, tags, "radroots:price")?;
+ forbid_tag_name(case_id, tags, "radroots:price_unit")?;
+ forbid_tag_name(case_id, tags, "radroots:quantity")?;
+ }
+ ArtifactVectorWitness::MixedFoodAndOperationalMarkers => {
+ require_exact_tag(case_id, tags, &["radroots:price", "3", "CAD"])?;
+ require_exact_tag(case_id, tags, &["radroots:price_unit", "lb"])?;
+ require_exact_tag(case_id, tags, &["radroots:quantity", "24", "lb"])?;
+ }
+ }
+ Ok(())
+}
+
+fn validate_raw_event_vector_witness(
+ case_id: &str,
+ witness: RawEventVectorWitness,
+ candidate: &Value,
+) -> Result<(), String> {
+ let candidate = candidate
+ .as_object()
+ .ok_or_else(|| format!("{case_id} raw event must be an object"))?;
+ let actual_keys = candidate
+ .keys()
+ .map(String::as_str)
+ .collect::<BTreeSet<_>>();
+ let expected_keys = [
+ "id",
+ "pubkey",
+ "created_at",
+ "kind",
+ "tags",
+ "content",
+ "sig",
+ ]
+ .into_iter()
+ .collect::<BTreeSet<_>>();
+ let (expected_tags, expected_content) = match witness {
+ RawEventVectorWitness::Profile => (
+ json!([]),
+ "{\"name\":\"victoria-farm\",\"display_name\":\"Victoria Farm\",\"about\":\"Seasonal produce from the Saanich Peninsula\",\"picture\":\"https://media.example/512f6a371c77694502e7d08f0b1f1080c7103ca90925bfe2fa23106aac11003a.png\",\"banner\":\"https://media.example/fbf3819415a76ea0d3ba71817578bb89c4903aa958e38f76f86578dfa8a35145.webp\",\"nip05\":\"farm@example.com\",\"bot\":false}",
+ ),
+ RawEventVectorWitness::GenericRootNote => (json!([]), "Generic root note"),
+ RawEventVectorWitness::Reply => (
+ json!([[
+ "e",
+ "1111111111111111111111111111111111111111111111111111111111111111",
+ "",
+ "reply"
+ ]]),
+ "Reply",
+ ),
+ RawEventVectorWitness::Comment => (
+ json!([[
+ "E",
+ "1111111111111111111111111111111111111111111111111111111111111111"
+ ]]),
+ "Comment",
+ ),
+ RawEventVectorWitness::DeletionRequest => (
+ json!([[
+ "e",
+ "1111111111111111111111111111111111111111111111111111111111111111"
+ ]]),
+ "Removed",
+ ),
+ RawEventVectorWitness::CalendarDate => (
+ json!([
+ ["d", "farmers-market-2026"],
+ ["title", "Moss Street Farmers Market"],
+ ["start", "2026-07-25"],
+ ["end", "2026-07-26"],
+ ["location", "Victoria, BC"],
+ [
+ "image",
+ "https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg"
+ ]
+ ]),
+ "Saturday market in Victoria",
+ ),
+ RawEventVectorWitness::CalendarTime => (
+ json!([
+ ["d", "farm-tour-2026"],
+ ["title", "Saanich Farm Tour"],
+ ["start", "1785003600"],
+ ["end", "1785007200"],
+ ["D", "20659"],
+ ["start_tzid", "America/Vancouver"],
+ [
+ "image",
+ "https://events.example/0a422cbf828d421341c40c678f4cfbd6451841760db126e5f5ac3d2e06fd80b8.jpeg"
+ ]
+ ]),
+ "A one-hour farm tour",
+ ),
+ RawEventVectorWitness::CalendarCollection => (json!([["d", "victoria-calendar"]]), ""),
+ RawEventVectorWitness::CalendarRsvp => (
+ json!([[
+ "a",
+ "31922:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa:market"
+ ]]),
+ "accepted",
+ ),
+ RawEventVectorWitness::Bud11Authorization => (json!([["t", "upload"]]), ""),
+ RawEventVectorWitness::Ephemeral => (json!([]), "Ephemeral"),
+ RawEventVectorWitness::Trade => (json!([["d", "trade-proposal"]]), ""),
+ RawEventVectorWitness::CommerceOrder => (json!([["d", "order-request"]]), ""),
+ RawEventVectorWitness::Group => (json!([["h", "victoria-growers"]]), ""),
+ RawEventVectorWitness::Operations => (json!([["d", "internal-operation"]]), ""),
+ };
+ if actual_keys != expected_keys
+ || candidate.get("id").and_then(Value::as_str) != Some(VECTOR_ZERO_ID)
+ || candidate.get("pubkey").and_then(Value::as_str) != Some(VECTOR_AUTHOR)
+ || candidate.get("created_at").and_then(Value::as_u64) != Some(VECTOR_CREATED_AT)
+ || candidate.get("kind").and_then(Value::as_u64) != Some(u64::from(witness.event_kind()))
+ || candidate.get("tags") != Some(&expected_tags)
+ || candidate.get("content").and_then(Value::as_str) != Some(expected_content)
+ || candidate.get("sig").and_then(Value::as_str) != Some(VECTOR_ZERO_SIGNATURE)
+ {
+ return Err(format!(
+ "{case_id} raw event kind or exact required/forbidden tag shape drifted"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_exact_tag_names(
+ case_id: &str,
+ tags: &[Value],
+ expected_names: &[&str],
+) -> Result<(), String> {
+ let names = tags
+ .iter()
+ .map(|tag| tag.get(0).and_then(Value::as_str))
+ .collect::<Option<Vec<_>>>()
+ .ok_or_else(|| format!("{case_id} contains an empty or non-string tag"))?;
+ if names != expected_names {
+ return Err(format!("{case_id} exact tag-name sequence drifted"));
+ }
+ Ok(())
+}
+
+fn require_exact_tag(case_id: &str, tags: &[Value], expected: &[&str]) -> Result<(), String> {
+ if tags.iter().any(|tag| {
+ tag.as_array().is_some_and(|fields| {
+ fields.len() == expected.len()
+ && fields
+ .iter()
+ .zip(expected)
+ .all(|(actual, expected)| actual.as_str() == Some(*expected))
+ })
+ }) {
+ Ok(())
+ } else {
+ Err(format!("{case_id} is missing its exact semantic marker"))
+ }
+}
+
+fn forbid_tag_name(case_id: &str, tags: &[Value], forbidden: &str) -> Result<(), String> {
+ if tags
+ .iter()
+ .any(|tag| tag.get(0).and_then(Value::as_str) == Some(forbidden))
+ {
+ Err(format!("{case_id} contains forbidden marker {forbidden}"))
+ } else {
+ Ok(())
+ }
+}
+
+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.allowlist != expected_allowlist_descriptor()
+ || manifest.predecessor_source_supersessions
+ != PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS
+ .iter()
+ .map(|path| (*path).to_owned())
+ .collect::<Vec<_>>()
+ || manifest.result_vector.canonical_path != VECTOR_RELATIVE
+ || manifest.result_vector.mirror_path != VECTOR_MIRROR_RELATIVE
+ || manifest.release.change_id != RELEASE_CHANGE_ID
+ {
+ return Err(format!("{MANIFEST_RELATIVE} shape drifted"));
+ }
+ let expected_predecessor = PREDECESSOR_ARTIFACTS
+ .iter()
+ .map(|spec| (spec.relative, spec.byte_length as u64, spec.sha256))
+ .collect::<Vec<_>>();
+ let actual_predecessor = manifest
+ .predecessor
+ .immutable_artifacts
+ .iter()
+ .map(|descriptor| {
+ (
+ descriptor.path.as_str(),
+ descriptor.byte_length,
+ descriptor.sha256.as_str(),
+ )
+ })
+ .collect::<Vec<_>>();
+ if actual_predecessor != expected_predecessor
+ || manifest.event_contract_registry.version != 7
+ || manifest.event_contract_registry.inventory.byte_length != REGISTRY_BYTE_LENGTH as u64
+ || manifest.event_contract_registry.inventory.sha256 != REGISTRY_SHA256
+ || manifest.event_contract_registry.sidecar.byte_length
+ != REGISTRY_SIDECAR_BYTE_LENGTH as u64
+ || manifest.event_contract_registry.sidecar.sha256 != REGISTRY_SIDECAR_SHA256
+ {
+ return Err(format!(
+ "{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"));
+ }
+ }
+ Ok(())
+}
+
+fn manifest_schema() -> Value {
+ json!({
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "$id": "https://radroots.org/contracts/event-codec/phase1-publication-allowlist-v1.schema.json",
+ "title": "Radroots Phase 1 Publication Allowlist Contract",
+ "type": "object",
+ "additionalProperties": false,
+ "required": [
+ "schema_version", "contract_id", "authority_id", "manifest_schema", "predecessor",
+ "event_contract_registry", "allowlist", "predecessor_source_supersessions",
+ "source_files", "result_vector", "release"
+ ],
+ "properties": {
+ "schema_version": {"const": SCHEMA_VERSION},
+ "contract_id": {"const": CONTRACT_ID},
+ "authority_id": {"const": AUTHORITY_ID},
+ "manifest_schema": {"$ref": "#/$defs/file"},
+ "predecessor": {
+ "type": "object", "additionalProperties": false,
+ "required": ["contract_id", "immutable_artifacts"],
+ "properties": {
+ "contract_id": {"const": PREDECESSOR_CONTRACT_ID},
+ "immutable_artifacts": {"type": "array", "minItems": 6, "maxItems": 6, "items": {"$ref": "#/$defs/file"}}
+ }
+ },
+ "event_contract_registry": {
+ "type": "object", "additionalProperties": false,
+ "required": ["version", "inventory", "sidecar", "evolution"],
+ "properties": {
+ "version": {"const": 7},
+ "inventory": {"$ref": "#/$defs/file"},
+ "sidecar": {"$ref": "#/$defs/file"},
+ "evolution": {"const": "immutable_registry_v7_plus_additive_phase1_publication_allowlist_v1"}
+ }
+ },
+ "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"],
+ "properties": {
+ "version": {"const": 1},
+ "contract_id": {"const": RADROOTS_PHASE1_PUBLICATION_ALLOWLIST_CONTRACT_ID},
+ "operation_id": {"const": OPERATION_ID},
+ "canonical_json_operation_id": {"const": CANONICAL_JSON_OPERATION_ID},
+ "input_type": {"const": "RadrootsPhase1PublicationArtifact"},
+ "output_type": {"const": "RadrootsPhase1AllowlistedPublicationArtifact"},
+ "allowed_leaves": {"type": "array", "minItems": 7, "maxItems": 7, "items": {"$ref": "#/$defs/leaf"}},
+ "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"},
+ "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}}
+ }
+ },
+ "predecessor_source_supersessions": {"type": "array", "minItems": 8, "maxItems": 8, "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"],
+ "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_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": {
+ "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": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
+ "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"],
+ "properties": {
+ "leaf": {"type": "string", "minLength": 1},
+ "kind": {"type": "integer", "minimum": 0},
+ "publication_role": {"enum": ["supporting", "root"]},
+ "authored_operation_id": {"type": "string", "minLength": 1},
+ "event_contract_id": {"type": "string", "minLength": 1}
+ }
+ }
+ }
+ })
+}
+
+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)?)
+}
+
+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 parse_toml(workspace_root: &Path, relative: &str) -> Result<toml::Value, 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 toml_string_array(label: &str, value: Option<&toml::Value>) -> Result<Vec<String>, String> {
+ value
+ .and_then(toml::Value::as_array)
+ .ok_or_else(|| format!("{label} must be an array"))?
+ .iter()
+ .map(|value| {
+ value
+ .as_str()
+ .map(str::to_owned)
+ .ok_or_else(|| format!("{label} values must be strings"))
+ })
+ .collect()
+}
+
+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_canonical_json<T: Serialize>(
+ relative: &str,
+ bytes: &[u8],
+ value: &T,
+) -> Result<(), String> {
+ if canonical_json_bytes(value)? != bytes {
+ return Err(format!("{relative} is not canonical pretty JSON"));
+ }
+ Ok(())
+}
+
+fn validate_json_schema(schema: &Value, instance: &Value) -> Result<(), String> {
+ let validator = jsonschema::validator_for(schema)
+ .map_err(|error| format!("compile {MANIFEST_SCHEMA_RELATIVE}: {error}"))?;
+ let errors = validator
+ .iter_errors(instance)
+ .map(|error| error.to_string())
+ .collect::<Vec<_>>();
+ if errors.is_empty() {
+ Ok(())
+ } else {
+ Err(format!(
+ "{MANIFEST_RELATIVE} violates its schema: {}",
+ errors.join("; ")
+ ))
+ }
+}
+
+fn sha256_hex(bytes: &[u8]) -> String {
+ hex::encode(Sha256::digest(bytes))
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::path::PathBuf;
+
+ fn workspace_root() -> PathBuf {
+ Path::new(env!("CARGO_MANIFEST_DIR"))
+ .parent()
+ .and_then(Path::parent)
+ .expect("xtask workspace root")
+ .to_path_buf()
+ }
+
+ #[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));
+ }
+ }
+
+ #[test]
+ fn allowlist_authorities_and_vector_are_current() {
+ 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_vector(&root).expect("allowlist vector");
+ }
+
+ #[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");
+ for (spec, vector) in INVALID_VECTOR_SPECS
+ .iter()
+ .copied()
+ .zip(suite.vectors.iter().skip(VALID_VECTOR_SPECS.len()))
+ {
+ let input = vector.input.as_object().expect("vector input");
+ let canonical_json = input
+ .get("canonical_json")
+ .and_then(Value::as_str)
+ .expect("canonical candidate");
+ let mut candidate: Value =
+ serde_json::from_str(canonical_json).expect("candidate JSON");
+ match spec.witness {
+ InvalidVectorWitness::Artifact(_) => {
+ candidate["draft"]["content"] = Value::String("witness drift".to_owned());
+ }
+ InvalidVectorWitness::RawEvent(_) => {
+ candidate["content"] = Value::String("witness drift".to_owned());
+ }
+ InvalidVectorWitness::NonEventRouteDelivery => {
+ candidate["product_surface"] = Value::String("delivery_event".to_owned());
+ }
+ }
+ assert!(
+ validate_invalid_vector_evidence(spec.id, spec, input, &candidate).is_err(),
+ "{} semantic witness accepted drift",
+ spec.id
+ );
+ }
+ }
+
+ #[test]
+ fn allowlist_manifest_schema_rejects_unknown_top_level_fields() {
+ let root = workspace_root();
+ let schema = manifest_schema();
+ let schema_bytes = canonical_json_bytes(&schema).expect("schema bytes");
+ let manifest = describe_manifest(&root, &schema_bytes).expect("manifest");
+ let mut value = serde_json::to_value(manifest).expect("manifest value");
+ validate_json_schema(&schema, &value).expect("valid manifest");
+ value["unexpected"] = Value::Bool(true);
+ validate_json_schema(&schema, &value).expect_err("unknown field must fail");
+ }
+}
diff --git a/tools/xtask/src/contract/phase1_publication_artifact.rs b/tools/xtask/src/contract/phase1_publication_artifact.rs
@@ -245,6 +245,50 @@ const RAW_IMMUTABLE_ARTIFACTS: &[ImmutableArtifactSpec] = &[
),
];
+const PUBLICATION_IMMUTABLE_ARTIFACTS: &[ImmutableArtifactSpec] = &[
+ ImmutableArtifactSpec::new(
+ MANIFEST_RELATIVE,
+ 89_464,
+ "0776e1d84c9366954047e75cdf12d9acc9a7108260157c3534f22067075a385a",
+ ),
+ ImmutableArtifactSpec::new(
+ MANIFEST_SCHEMA_RELATIVE,
+ 11_972,
+ "1d72cee2754e7ac45105d79b1ecf7d44251991be7a18ba106166e962000e8320",
+ ),
+ ImmutableArtifactSpec::new(
+ MANIFEST_SHA256_RELATIVE,
+ 65,
+ "586c0985b502f22241b4d90f2ecb475d43953fc3ddd66d6fbfa3b3eb9cf34444",
+ ),
+ ImmutableArtifactSpec::new(
+ VECTOR_RELATIVE,
+ 23_113,
+ "ec18c687d5b0710a48624ddb620d89157e6b645dbea8bb91c62e3a111d20c622",
+ ),
+ ImmutableArtifactSpec::new(
+ VECTOR_MIRROR_RELATIVE,
+ 23_113,
+ "ec18c687d5b0710a48624ddb620d89157e6b645dbea8bb91c62e3a111d20c622",
+ ),
+ ImmutableArtifactSpec::new(
+ VECTOR_EXECUTOR_RELATIVE,
+ 34_582,
+ "7a31169eac4217a38cb3ef25eb9213f2f89e11fb17e76ceaf7449b34225e98af",
+ ),
+];
+
+pub(super) const PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS: &[&str] = &[
+ CHANGELOG_RELATIVE,
+ OPERATIONS_RELATIVE,
+ RELEASE_RELATIVE,
+ "crates/event_codec/README",
+ "crates/event_codec/src/wire/publication.rs",
+ "tools/xtask/src/contract.rs",
+ "tools/xtask/src/contract/phase1_publication_artifact.rs",
+ "tools/xtask/src/main.rs",
+];
+
const GOVERNED_COMPILER_TABLES: &[(&str, &str, &str)] = &[
(
"crates/core/Cargo.toml",
@@ -542,17 +586,24 @@ struct ValidatedResultVector {
pub(crate) fn write_phase1_publication_artifact_manifest(
workspace_root: &Path,
) -> Result<(), String> {
- with_artifact_bundle_transaction(workspace_root, |transaction| {
- transaction.write(expected_artifacts(workspace_root)?)?;
- validate_manifest_under_lock(workspace_root)
- })
+ validate_immutable_phase1_publication_artifact_predecessor(workspace_root)?;
+ Err(
+ "Phase 1 publication artifact v1 is an immutable predecessor and cannot be rewritten; write the active Phase 1 publication allowlist successor instead"
+ .to_owned(),
+ )
}
pub(crate) fn validate_phase1_publication_artifact_manifest(
workspace_root: &Path,
) -> Result<(), String> {
+ validate_immutable_phase1_publication_artifact_predecessor(workspace_root)
+}
+
+pub(crate) fn validate_immutable_phase1_publication_artifact_predecessor(
+ workspace_root: &Path,
+) -> Result<(), String> {
with_artifact_bundle_transaction(workspace_root, |_| {
- validate_manifest_under_lock(workspace_root)
+ validate_immutable_phase1_publication_artifact_predecessor_under_lock(workspace_root)
})
}
@@ -599,6 +650,93 @@ fn validate_manifest_under_lock(workspace_root: &Path) -> Result<(), String> {
Ok(())
}
+pub(super) fn validate_immutable_phase1_publication_artifact_predecessor_under_lock(
+ workspace_root: &Path,
+) -> Result<(), String> {
+ for spec in PUBLICATION_IMMUTABLE_ARTIFACTS {
+ let bytes = read_regular_file(workspace_root, spec.relative)?;
+ if bytes.len() != spec.byte_length || sha256_hex(&bytes) != spec.sha256 {
+ return Err(format!(
+ "immutable Phase 1 publication artifact predecessor {} drifted",
+ spec.relative
+ ));
+ }
+ }
+
+ let manifest_bytes = read_regular_file(workspace_root, MANIFEST_RELATIVE)?;
+ let manifest: Value = serde_json::from_slice(&manifest_bytes)
+ .map_err(|error| format!("parse {MANIFEST_RELATIVE}: {error}"))?;
+ if manifest.get("schema_version").and_then(Value::as_u64) != Some(1)
+ || manifest.get("contract_id").and_then(Value::as_str)
+ != Some("radroots_event_codec.phase1_publication_artifact_v1")
+ || manifest.get("authority_id").and_then(Value::as_str)
+ != Some("phase1_publication_artifact_v1")
+ {
+ return Err(format!("{MANIFEST_RELATIVE} identity drifted"));
+ }
+ let sidecar = read_regular_file(workspace_root, MANIFEST_SHA256_RELATIVE)?;
+ if sidecar != format!("{}\n", sha256_hex(&manifest_bytes)).as_bytes() {
+ return Err(format!(
+ "{MANIFEST_SHA256_RELATIVE} does not authenticate its immutable manifest"
+ ));
+ }
+
+ let superseded = PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS
+ .iter()
+ .copied()
+ .collect::<BTreeSet<_>>();
+ if superseded.len() != PUBLICATION_SUCCESSOR_SUPERSEDED_PATHS.len() {
+ return Err("publication predecessor supersession paths must be unique".to_owned());
+ }
+ let sources = manifest
+ .get("source_files")
+ .and_then(Value::as_array)
+ .ok_or_else(|| format!("{MANIFEST_RELATIVE} has no source_files array"))?;
+ let mut seen = BTreeSet::new();
+ for source in sources {
+ let path = descriptor_path(source, "publication predecessor source")?;
+ if !seen.insert(path) {
+ return Err(format!("{MANIFEST_RELATIVE} duplicates source {path}"));
+ }
+ if !superseded.contains(path) {
+ validate_value_descriptor(workspace_root, source, "publication predecessor source")?;
+ }
+ }
+ for path in superseded {
+ if !seen.contains(path) {
+ return Err(format!(
+ "publication predecessor supersession path {path} is not predecessor-bound"
+ ));
+ }
+ }
+
+ validate_value_descriptor(
+ workspace_root,
+ manifest
+ .get("manifest_schema")
+ .ok_or_else(|| format!("{MANIFEST_RELATIVE} has no manifest_schema"))?,
+ "publication predecessor schema",
+ )?;
+ for pointer in [
+ "/event_contract_registry/inventory",
+ "/event_contract_registry/sidecar",
+ ] {
+ validate_value_descriptor(
+ workspace_root,
+ manifest
+ .pointer(pointer)
+ .ok_or_else(|| format!("{MANIFEST_RELATIVE} has no {pointer}"))?,
+ "publication predecessor registry",
+ )?;
+ }
+ if read_regular_file(workspace_root, VECTOR_RELATIVE)?
+ != read_regular_file(workspace_root, VECTOR_MIRROR_RELATIVE)?
+ {
+ return Err("immutable Phase 1 publication artifact vector mirror drifted".to_owned());
+ }
+ Ok(())
+}
+
fn expected_artifacts(workspace_root: &Path) -> Result<Vec<GeneratedArtifact>, String> {
validate_immutable_raw_predecessor_under_lock(workspace_root)?;
validate_source_contract(workspace_root)?;
@@ -1345,7 +1483,7 @@ fn descriptor_path<'a>(descriptor: &'a Value, label: &str) -> Result<&'a str, St
.ok_or_else(|| format!("{label} has no path"))
}
-fn source_specs(workspace_root: &Path) -> Result<Vec<(String, String)>, String> {
+pub(super) fn source_specs(workspace_root: &Path) -> Result<Vec<(String, String)>, String> {
let mut specs = Vec::new();
for root in PUBLIC_SOURCE_ROOTS {
for path in regular_file_inventory(workspace_root, root)? {
diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs
@@ -23,6 +23,7 @@ fn usage() {
eprintln!(" cargo xtask contract source-maintenance-manifest [--write]");
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 knowledge-manifest [--write]");
eprintln!(" cargo xtask dto-roots --check|--write");
eprintln!(" cargo xtask release preflight");
@@ -149,6 +150,16 @@ fn run_contract(args: &[String]) -> Result<(), String> {
.to_string(),
),
},
+ Some("phase1-publication-allowlist-manifest") => match &args[1..] {
+ [] => contract::validate_phase1_publication_allowlist_manifest(&workspace_root()),
+ [flag] if flag == "--write" => {
+ contract::write_phase1_publication_allowlist_manifest(&workspace_root())
+ }
+ _ => Err(
+ "phase1-publication-allowlist-manifest 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())
@@ -281,6 +292,12 @@ mod tests {
])
.expect_err("invalid Phase 1 publication manifest mode");
assert!(invalid_publication.contains("exactly --write"));
+ let invalid_publication_allowlist = run_contract(&[
+ "phase1-publication-allowlist-manifest".to_string(),
+ "--invalid".to_string(),
+ ])
+ .expect_err("invalid Phase 1 publication allowlist manifest mode");
+ assert!(invalid_publication_allowlist.contains("exactly --write"));
let unknown_root = run(&["unknown".to_string()]).expect_err("unknown command");
assert!(unknown_root.contains("unknown command"));
@@ -384,6 +401,8 @@ mod tests {
.expect("contract raw-source rebuild manifest");
run_contract(&["phase1-publication-artifact-manifest".to_string()])
.expect("contract Phase 1 publication artifact manifest");
+ run_contract(&["phase1-publication-allowlist-manifest".to_string()])
+ .expect("contract Phase 1 publication allowlist manifest");
run_contract(&["knowledge-manifest".to_string()]).expect("contract knowledge manifest");
}
}