commit 93879c2a8f9696fc6453267ea427d4a9e9f63ffc
parent 023a88f96da7895a10c240704849c79b90e80a34
Author: triesap <tyson@radroots.org>
Date: Sun, 19 Jul 2026 21:04:44 +0000
event: evaluate NIP-09 suppression
- add a pure evaluator over verified targets and admitted requests
- enforce same-author event references and inclusive address cutoffs
- return canonical deterministic decisions and supporting evidence
- execute a fixed signed suppression corpus and exact operation authority
Diffstat:
13 files changed, 3385 insertions(+), 88 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
@@ -159,6 +159,11 @@ publish policy both pass for the same source revision.
creates and transports a request only; it provides no target lookup,
authorship decision, deletion authorization, store mutation, relay-effect,
or deletion-effect semantics.
+- NIP-09 suppression evaluation is now a separate pure operation over one
+ signature-verified candidate and admitted requests. It enforces same-author
+ direct-event and inclusive address-cutoff rules, keeps kind `5` immune,
+ ignores advisory kinds, returns canonical evidence independent of input
+ order, and never mutates raw events or storage.
### Removed
diff --git a/contracts/conformance/vectors/deletion/suppression.v1.json b/contracts/conformance/vectors/deletion/suppression.v1.json
@@ -0,0 +1,609 @@
+{
+ "suite": "nip09_suppression_evaluator",
+ "contract_version": "1.0.0",
+ "vectors": [
+ {
+ "id": "nip09_suppress_no_requests_visible",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_no_authorized_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_same_author_event_reference",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"event reference a\",\"sig\":\"36ab6c25bcc0fa22da55a566121cf43210f90a8f0f9243736fd24ff57b4799ac09cf017ef3cd132464b728bb5ff87603535fbb1b566413ed5f34a60c1838a809\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_same_author_nonmatching_reference",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"9b3d3a1ca7f4a7019a17c86465cadfeaa262ebc76042d2a5a07971e09b36d14c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"120bd6d1a82eb11b24f05316f3254c0fa6cb87fcc909d7200a963e40207f5e77\"]],\"content\":\"other reference\",\"sig\":\"9765abc98be250a2b60b6f3aa33e695ff58e0aa5f5b2e927cbb878acdb25b645a8d1a9d03f6cb9de69448c4ec6bc7e6f2cc5276a3865ac0a4d4c8daf4b331a48\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_no_authorized_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_wrong_author_exact_event_reference",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"403981275c07a7c67d35552ee3dbc416c20b770d5485216ff2d1b18ad287588b\",\"pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"wrong author event\",\"sig\":\"e7f2e84135c979c0d3fa815d3b9082163152e37d55cf0f40e21e4df81baee81efe1365b0011da940b63efb7dc9d13fb7d84d990aee4a04e4dd582790ff205770\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_request_author_mismatch"
+ }
+ },
+ {
+ "id": "nip09_suppress_event_reference_predates_target",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"a75e12b2175feafcad0763545cbbf31c118915f60d97572bb68fafbcc533f6be\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100190,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"pre-target exact event\",\"sig\":\"c2fbe879659b5da522dbb400ac08174f522482a8077fa10c868c1a395598e2e40175d1f232964d897f84e9e710de8c9a2b0feff088cead33a5d1879a96fa1d7d\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "a75e12b2175feafcad0763545cbbf31c118915f60d97572bb68fafbcc533f6be"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_deletion_request_immune_event_reference",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"a8fb77cd006005bffaa04f49ba01560dccfc2ca9875d79bd0a7bdc3862553df5\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"a9bbfa0344854ced08a13230a3a84b2d3971f5bf08f1f6af25f1547eec307a89\"]],\"content\":\"delete a deletion\",\"sig\":\"52a7356d38a5a49c32094905a5b4ff5c4adb41146a6b046377541cd91cf44b2f0cdb7640aab556c8375fafc8460f6f3076731d12b9923cc15538d54dcdd47f6a\"}"
+ ],
+ "target_event_json": "{\"id\":\"a9bbfa0344854ced08a13230a3a84b2d3971f5bf08f1f6af25f1547eec307a89\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":5,\"tags\":[[\"e\",\"120bd6d1a82eb11b24f05316f3254c0fa6cb87fcc909d7200a963e40207f5e77\"]],\"content\":\"original deletion\",\"sig\":\"5f17a714f308ca33c2d5f1e4b52d2a56a406474ae0e6a1cd4482b3a3cf116fdab7cebe77e5a6cc18a2490f32292bcdf4df08ab8d3a2d507df4cd99c39e6fb1f7\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_request_immune"
+ }
+ },
+ {
+ "id": "nip09_suppress_deletion_request_immune_mixed_references",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"6cebea08ac79c820dac9936444a818dd8aa89dffb5b0b251b21edea266463860\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100211,\"kind\":5,\"tags\":[[\"e\",\"a9bbfa0344854ced08a13230a3a84b2d3971f5bf08f1f6af25f1547eec307a89\"],[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"mixed deletion target\",\"sig\":\"464b930b47df90bf5c9d6f0138c082d71c3fb84f49755168e690672cc1a47635c507518f876cc9aceff60765c3db9a8be1b774a3322b79dbe82bd45f3c06de8b\"}"
+ ],
+ "target_event_json": "{\"id\":\"a9bbfa0344854ced08a13230a3a84b2d3971f5bf08f1f6af25f1547eec307a89\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":5,\"tags\":[[\"e\",\"120bd6d1a82eb11b24f05316f3254c0fa6cb87fcc909d7200a963e40207f5e77\"]],\"content\":\"original deletion\",\"sig\":\"5f17a714f308ca33c2d5f1e4b52d2a56a406474ae0e6a1cd4482b3a3cf116fdab7cebe77e5a6cc18a2490f32292bcdf4df08ab8d3a2d507df4cd99c39e6fb1f7\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_request_immune"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_cutoff_before_target",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"1473dabefc767c7a2a16ce716e0cbcd1ea463299c6e1e4d1c805672669012bd2\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100199,\"kind\":5,\"tags\":[[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"address before\",\"sig\":\"7bdf964d33f45eaeb81fbc671ae994dfdcf61dcac6c37f70831be76148ff9e05a2a0abe673a8655a2d3acaffdfba5aaa719cc00a26b9f3938626839681e991dd\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100199,
+ "request_id": "1473dabefc767c7a2a16ce716e0cbcd1ea463299c6e1e4d1c805672669012bd2"
+ },
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_address_cutoff_precedes_target"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_cutoff_equal_target",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"3d75aa21ad472076c331419a3f2e73284ef8083f79d11f47b4c79e76604dc775\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":5,\"tags\":[[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"address equal\",\"sig\":\"aeda08b49690df9655012e964b69dca4c9f5f1545423378581875b8fb1ea27719ee0ba1c6c54a06c6938dec8b967379403c45d43e208b6746715f0cdf8c26e73\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100200,
+ "request_id": "3d75aa21ad472076c331419a3f2e73284ef8083f79d11f47b4c79e76604dc775"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_cutoff_after_target",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"086915e7b1c08ba14f5c8fabca8490b56b022b093ce21490d8f706df83c13e2c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100201,\"kind\":5,\"tags\":[[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"address after\",\"sig\":\"49dc8bd554da237d73ac266f893895df0509e4cf3b5eb372b393c42b8872bf1afb1d1922be622acbf6b811657d15beac4f6a2e69d24206b185db3135ed6cdb70\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100201,
+ "request_id": "086915e7b1c08ba14f5c8fabca8490b56b022b093ce21490d8f706df83c13e2c"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_wrong_kind",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"b24aa726605e907b6d2733e8e36e47f890e544583c976fa0347b054d706e74ec\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"3:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"wrong kind\",\"sig\":\"a03a7de08ba411613602fd0d8d04f70dbe81dd194bb7a556691595a2830a0334883cb0b45d9e6ddb253aed25de405da567c4c9f48a2064172ddef88959b7209c\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_no_authorized_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_wrong_identifier",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"8388ea59c1f08f5091b3332e172145ef09bc671e7ad71e44c198867a66578824\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"39999:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:orchard:east/plot\"]],\"content\":\"wrong identifier\",\"sig\":\"85f06ad83418d262150642d1b64eea745f410232c6589d7cde7fd63fd896fd66b644db93a275cbeceadebf0f4976ee9cdee38ef2e6f84c76364e9309a4c614bb\"}"
+ ],
+ "target_event_json": "{\"id\":\"198aeb65fa856b3eb7b5a7fe36fa90da70fbdbb1cb88b31a3893d07c9437c796\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":39999,\"tags\":[[\"d\",\"orchard:west/plot\"]],\"content\":\"opaque addressable\",\"sig\":\"cc3db13caa8e407e842d5a977f720624a17364bf617179aa5a9318e7ab111818b82d20799e074092522fa49c8b709e4b8af1877a0599c63797f81a71689b9638\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_no_authorized_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_wrong_pubkey",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"39ab140373199234d51f13034f0383a6d7a492d49303cf0b5c08f31d876674f6\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"0:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:\"]],\"content\":\"wrong pubkey\",\"sig\":\"efc60d415f344ce7103ad9179a6642c5823caf2e4b44f642276130f8be7e9400d882ce1299bdf898bdbfc01efd105f525c2a3dc55dd0d56531b74ed173b171a6\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_no_authorized_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_wrong_author_address_reference",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"616960881e5bdaa21bff166269193e8503fc0d8f55507ec955a55421c34c46c5\",\"pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"wrong author address\",\"sig\":\"515d51aca57cecaeb94111daf508979c6b78523ef4005ffed9966a39beeaea26c01b727482f82ddea88db8f3333f4e00dd6fa1ad4eddd0ec43ba213bb44f96f5\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_request_author_mismatch"
+ }
+ },
+ {
+ "id": "nip09_suppress_replaceable_kind_0",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"086915e7b1c08ba14f5c8fabca8490b56b022b093ce21490d8f706df83c13e2c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100201,\"kind\":5,\"tags\":[[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"address after\",\"sig\":\"49dc8bd554da237d73ac266f893895df0509e4cf3b5eb372b393c42b8872bf1afb1d1922be622acbf6b811657d15beac4f6a2e69d24206b185db3135ed6cdb70\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100201,
+ "request_id": "086915e7b1c08ba14f5c8fabca8490b56b022b093ce21490d8f706df83c13e2c"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_replaceable_kind_3",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"e10db54031bbff5da53d70d7bba7f6ee3ce4497152618cad9e2ea78567874169\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"3:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"replaceable kind 3\",\"sig\":\"816d45c35ebdef5495ec1060f52fad19ea88b4d093431e1ae194ca5e7380145950b2b03dea2dd701e13a4e99c0c6071bb2e45e59782ce3a0547ef8a4ff6dadab\"}"
+ ],
+ "target_event_json": "{\"id\":\"9b7a698850a451d198e792be95a10bb093ccfd65ebbdc8395d4282c930f281ae\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":3,\"tags\":[],\"content\":\"contacts\",\"sig\":\"30e46ce6dc57368992a86e2eace20470164de87aeecc08c9717231af4b75eb45e6945fc3b07be3cbf640a2b7fb75c9811903f94027bbe39374cc1a06155a4aea\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "3:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "e10db54031bbff5da53d70d7bba7f6ee3ce4497152618cad9e2ea78567874169"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_replaceable_kind_10000",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"e512278a22f532facc56739af02486b579a680b7a71ab0e064b86875049f79b1\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"10000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"replaceable kind 10000\",\"sig\":\"b814886b0f702015d37ed545e14771ae9c712ca57ad7e81437add6e109060d346b1695c7abb6b1810198d89af6fe0a50ee5eb54ed4676024652276932ea0d4de\"}"
+ ],
+ "target_event_json": "{\"id\":\"db73709e2333601fa97d8ade507c1fb046d37129d32b99e4ff24b316895399e1\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":10000,\"tags\":[],\"content\":\"replaceable lower\",\"sig\":\"8152e75fefbfa5e361402a70c4ee796d5cc6e11cf98ea447e2e7f53a8ad6b8618edea1dcdd8194deaf2304c5e53b442f56fc9e8809b062d0f6a1f07ea1caddb2\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "10000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "e512278a22f532facc56739af02486b579a680b7a71ab0e064b86875049f79b1"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_replaceable_kind_19999",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"9bafe3e4a5ea4bc2f6c2027c096bab9962949d0a172003a3177f7739dc1e55eb\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"19999:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"replaceable kind 19999\",\"sig\":\"8d8ecdcf165819bff0f173289a8ad13758b812f36a12b43de37f1cea468a893e89ae542121b6e3d3fb75b52a946ef2ac81dfad209350ece01b4548d4a04a61cc\"}"
+ ],
+ "target_event_json": "{\"id\":\"22ae552d598bd60490890db49e4738fc6d10376dc9fb9765fb319b3d3d72a472\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":19999,\"tags\":[],\"content\":\"replaceable upper\",\"sig\":\"16db6c2afd5eaf97143d0ecf1ab6535c2df08e1d61dffcf2feabea54dca1a42d3f11b1d82ac77bb60b4498469a25823c1adc6ae133a00ddf77c86ba6f6ccbb60\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "19999:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "9bafe3e4a5ea4bc2f6c2027c096bab9962949d0a172003a3177f7739dc1e55eb"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_addressable_kind_30000_empty_identifier",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"9900ec21cbddfe6598f64983cf52232ff3b59250fa41810dc8ca883a22c21d49\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"addressable kind 30000\",\"sig\":\"a8cee6e2fe394cf26ac783a57d275a8891f716cc8077a85e488fd02d04c9c821ce65e98543b022b1cf62733d2e7a0fbfdfaaec2c49bad68f761d49dfd09ad262\"}"
+ ],
+ "target_event_json": "{\"id\":\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable initial\",\"sig\":\"a60cffce8efd7b1a1372d6d0b175afc49bcd1e7a88b94f3e57ef212a4b7f9dbbb91c058c2af11209a20b2cd30d6ce74886fcf770c726dc8c3e20d0081e0ec1fa\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "9900ec21cbddfe6598f64983cf52232ff3b59250fa41810dc8ca883a22c21d49"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_addressable_kind_39999_opaque_identifier",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"9a308e463030f356e68abefba2bc9c766c766f509ec6809c8709ebe7da8d355e\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"39999:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:orchard:west/plot\"]],\"content\":\"addressable kind 39999\",\"sig\":\"bd7b5f63aaf54a80283da21103556e235851b9e31e5b668808ceff8f6d1af303b4334b7855478b277bca7da1775a630b46671ca195afd1f920752a7cb391afb2\"}"
+ ],
+ "target_event_json": "{\"id\":\"198aeb65fa856b3eb7b5a7fe36fa90da70fbdbb1cb88b31a3893d07c9437c796\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":39999,\"tags\":[[\"d\",\"orchard:west/plot\"]],\"content\":\"opaque addressable\",\"sig\":\"cc3db13caa8e407e842d5a977f720624a17364bf617179aa5a9318e7ab111818b82d20799e074092522fa49c8b709e4b8af1877a0599c63797f81a71689b9638\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "39999:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:orchard:west/plot",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "9a308e463030f356e68abefba2bc9c766c766f509ec6809c8709ebe7da8d355e"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_combined_event_and_address_references",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"eec90d8cb7e493d68b197310454c1f5b92828af5973aa26ee8d5fb82e0f30434\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"e\",\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\"],[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"combined references\",\"sig\":\"3f8203c5d57289e9ebdc5c2e2ab7f487ee60e409d5d4667057eba0ad10153c59aa8236d37e7d177a693243f9c9896cf05f8afd971dfc358438a55cc3ff5ebfaf\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "eec90d8cb7e493d68b197310454c1f5b92828af5973aa26ee8d5fb82e0f30434"
+ },
+ "event_reference": {
+ "request_id": "eec90d8cb7e493d68b197310454c1f5b92828af5973aa26ee8d5fb82e0f30434"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_and_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_event_reference_with_stale_address",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"a0165c75548878cd6a959ab2a12e3826c1b118240c328cc0470d441b7306edf0\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100199,\"kind\":5,\"tags\":[[\"e\",\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\"],[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"exact event stale address\",\"sig\":\"512a96994d5f357abd9cd9c7cc7a00658ec4ee7cbe4bb6d83fbe26b22c75755f92c43f89b4ff8f12ce0c06e44fa1277c370aac1de4551543831a6640cb1496f4\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100199,
+ "request_id": "a0165c75548878cd6a959ab2a12e3826c1b118240c328cc0470d441b7306edf0"
+ },
+ "event_reference": {
+ "request_id": "a0165c75548878cd6a959ab2a12e3826c1b118240c328cc0470d441b7306edf0"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_kind_advisory_diagnostics_ignored",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"859689351e1457702ea9fb97e056185781bd779dc3889b42b9bed49936691ea9\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"],[\"k\"],[\"k\",\"bogus\"],[\"k\",\"1\"],[\"k\",\"1\"],[\"k\",\"2\"]],\"content\":\"ignored kind diagnostics\",\"sig\":\"5dfaa28b172be180313aee8e204b08a2a4a208f81ecdd1727f0d55bddfd57fbd62ac5cc0cbda5f17f8f4bd4bf758f77c0685723859d9269e0a2c7f39bde88354\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "859689351e1457702ea9fb97e056185781bd779dc3889b42b9bed49936691ea9"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_duplicate_raw_targets_deduplicated",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"ab329bb4dcb4e7b3b706f44b1212e8464d0c1a622e0f7d4cb5da62b5b7291ade\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"e\",\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\"],[\"e\",\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\"],[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"],[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"duplicate raw references\",\"sig\":\"7479c3b44811e401d3cf51140779db9b2b9c6b04194f643e01c4b5f2f3adfa54812ebb1f36d83cdc5d8dcc72eb88335fae281c9383135dbe7da41c0e5a1287fe\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "ab329bb4dcb4e7b3b706f44b1212e8464d0c1a622e0f7d4cb5da62b5b7291ade"
+ },
+ "event_reference": {
+ "request_id": "ab329bb4dcb4e7b3b706f44b1212e8464d0c1a622e0f7d4cb5da62b5b7291ade"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_and_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_max_address_cutoff_forward_order",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"88979696e9bbc7762dd224aa4a7c95dbe19567e0d83667185472a4124105c7aa\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100230,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 230\",\"sig\":\"420f8775b6e5454e8fb69207f00b1c9d215e3f3f837e3433d1c939acba5bb7dcea64db45edcfbeb3c621696cf9c58ecb469e298a2377ca28c399a8e2c5dd97bd\"}",
+ "{\"id\":\"2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100240,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 240 a\",\"sig\":\"7562e4477358a0788f7ca4052944fb647235910504de38280b3737b0e43cb1628beb4a19312d83e93438a131d9ab7239193696bc61d70e09e370c98023c45cd2\"}"
+ ],
+ "target_event_json": "{\"id\":\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable initial\",\"sig\":\"a60cffce8efd7b1a1372d6d0b175afc49bcd1e7a88b94f3e57ef212a4b7f9dbbb91c058c2af11209a20b2cd30d6ce74886fcf770c726dc8c3e20d0081e0ec1fa\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100240,
+ "request_id": "2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_max_address_cutoff_reverse_order",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100240,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 240 a\",\"sig\":\"7562e4477358a0788f7ca4052944fb647235910504de38280b3737b0e43cb1628beb4a19312d83e93438a131d9ab7239193696bc61d70e09e370c98023c45cd2\"}",
+ "{\"id\":\"88979696e9bbc7762dd224aa4a7c95dbe19567e0d83667185472a4124105c7aa\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100230,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 230\",\"sig\":\"420f8775b6e5454e8fb69207f00b1c9d215e3f3f837e3433d1c939acba5bb7dcea64db45edcfbeb3c621696cf9c58ecb469e298a2377ca28c399a8e2c5dd97bd\"}"
+ ],
+ "target_event_json": "{\"id\":\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable initial\",\"sig\":\"a60cffce8efd7b1a1372d6d0b175afc49bcd1e7a88b94f3e57ef212a4b7f9dbbb91c058c2af11209a20b2cd30d6ce74886fcf770c726dc8c3e20d0081e0ec1fa\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100240,
+ "request_id": "2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_later_revision_survives_address_cutoff",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"88979696e9bbc7762dd224aa4a7c95dbe19567e0d83667185472a4124105c7aa\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100230,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 230\",\"sig\":\"420f8775b6e5454e8fb69207f00b1c9d215e3f3f837e3433d1c939acba5bb7dcea64db45edcfbeb3c621696cf9c58ecb469e298a2377ca28c399a8e2c5dd97bd\"}",
+ "{\"id\":\"2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100240,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 240 a\",\"sig\":\"7562e4477358a0788f7ca4052944fb647235910504de38280b3737b0e43cb1628beb4a19312d83e93438a131d9ab7239193696bc61d70e09e370c98023c45cd2\"}"
+ ],
+ "target_event_json": "{\"id\":\"d35339c16ecb0baff3149324c96c01983e4263278a1e1d6f58ad41f538bcd3ed\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100260,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable later\",\"sig\":\"55e1bffad1fa0553c1b67f27a15f226c3ea18ba2a2716f12a8ae46cdd007d2c3b2a6d36997521de8a70cc2914dceb1f0fe1664176e27c458069f7c04b9383ad4\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100240,
+ "request_id": "2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c"
+ },
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_address_cutoff_precedes_target"
+ }
+ },
+ {
+ "id": "nip09_suppress_equal_max_cutoff_uses_lowest_request_id",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100240,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 240 a\",\"sig\":\"7562e4477358a0788f7ca4052944fb647235910504de38280b3737b0e43cb1628beb4a19312d83e93438a131d9ab7239193696bc61d70e09e370c98023c45cd2\"}",
+ "{\"id\":\"5bb1267cbc05f8c9365cc6f73ab79cc800c85fdfbdbe4e3d6d319574937244f6\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100240,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 240 b\",\"sig\":\"55d93ad2d0702eb9fdb8e019c5802b75ecef439951ab77c76b18bac5d09a2d465fc6e4618dcc43bb7bfb62690e9cae0d60c5dba99c8f3ab24f3231b4e45c8bdf\"}"
+ ],
+ "target_event_json": "{\"id\":\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable initial\",\"sig\":\"a60cffce8efd7b1a1372d6d0b175afc49bcd1e7a88b94f3e57ef212a4b7f9dbbb91c058c2af11209a20b2cd30d6ce74886fcf770c726dc8c3e20d0081e0ec1fa\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100240,
+ "request_id": "2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_multiple_event_references_use_lowest_request_id",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"event reference a\",\"sig\":\"36ab6c25bcc0fa22da55a566121cf43210f90a8f0f9243736fd24ff57b4799ac09cf017ef3cd132464b728bb5ff87603535fbb1b566413ed5f34a60c1838a809\"}",
+ "{\"id\":\"2284746bed9c30d7d7829553a42fa5e53200bf8bd6160a99076ee97e9b3aa75b\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100211,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"event reference b\",\"sig\":\"920891d1e65e9b476e0a0810cde3583bd92586c3e07feb650727ade2aff5487c08f0c37fc669bfd3137082991fc1ac0e467987c3b8468a11107b5b4fcd59a3a5\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "2284746bed9c30d7d7829553a42fa5e53200bf8bd6160a99076ee97e9b3aa75b"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_repeated_request_is_idempotent",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"event reference a\",\"sig\":\"36ab6c25bcc0fa22da55a566121cf43210f90a8f0f9243736fd24ff57b4799ac09cf017ef3cd132464b728bb5ff87603535fbb1b566413ed5f34a60c1838a809\"}",
+ "{\"id\":\"fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"event reference a\",\"sig\":\"36ab6c25bcc0fa22da55a566121cf43210f90a8f0f9243736fd24ff57b4799ac09cf017ef3cd132464b728bb5ff87603535fbb1b566413ed5f34a60c1838a809\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_unauthorized_event_plus_stale_authorized_address",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"23f5fa17f6c32fbac2be7d3bbac04f772c8736610c508bb058d5176f07fb83cd\",\"pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"created_at\":1800100270,\"kind\":5,\"tags\":[[\"e\",\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\"]],\"content\":\"unauthorized exact addressable event\",\"sig\":\"554ecdb5a70ae3a5a57689a531635876d71e66a9f39deaff4384f1f3a983824c11566c8e007487ef664abdde03f248b18404254f958c5cf20ae2d9cb0c78e7b6\"}",
+ "{\"id\":\"0e2e9cb53f3c98e254fdbba41f6f87f470dceed991d2fee7c06db5957690b182\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100199,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"stale authorized address\",\"sig\":\"2d3b1a3f296eb2558af31611426af71f705a503f6671f9528d6752214fbbbb2b7b47837b9acc7e3f69f1bd068058cad735ea48fbd6ff1f2a1f793eefad165907\"}"
+ ],
+ "target_event_json": "{\"id\":\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable initial\",\"sig\":\"a60cffce8efd7b1a1372d6d0b175afc49bcd1e7a88b94f3e57ef212a4b7f9dbbb91c058c2af11209a20b2cd30d6ce74886fcf770c726dc8c3e20d0081e0ec1fa\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100199,
+ "request_id": "0e2e9cb53f3c98e254fdbba41f6f87f470dceed991d2fee7c06db5957690b182"
+ },
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_address_cutoff_precedes_target"
+ }
+ },
+ {
+ "id": "nip09_suppress_exact_event_deletes_later_replacement",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"5d0e1e5fbd9f7f8afc20b7b53b7387fb5bbeb4cfaa9ab715833efc3ed248f83d\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100270,\"kind\":5,\"tags\":[[\"e\",\"d35339c16ecb0baff3149324c96c01983e4263278a1e1d6f58ad41f538bcd3ed\"]],\"content\":\"exact later replacement\",\"sig\":\"c7beed981d08bb5be1b1938baaac92554bfe0133ac1de85c56c0205c4adcd3f52f2895819faa2d8c1733fdad6589c6fba9547135e5b580a230881621f5d083b8\"}"
+ ],
+ "target_event_json": "{\"id\":\"d35339c16ecb0baff3149324c96c01983e4263278a1e1d6f58ad41f538bcd3ed\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100260,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable later\",\"sig\":\"55e1bffad1fa0553c1b67f27a15f226c3ea18ba2a2716f12a8ae46cdd007d2c3b2a6d36997521de8a70cc2914dceb1f0fe1664176e27c458069f7c04b9383ad4\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "5d0e1e5fbd9f7f8afc20b7b53b7387fb5bbeb4cfaa9ab715833efc3ed248f83d"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ }
+ ]
+}
diff --git a/contracts/event_boundary_matrix.md b/contracts/event_boundary_matrix.md
@@ -211,19 +211,34 @@ unprovable, so a differing `k` advisory is not promoted to a conflict.
Admission proves only that the kind-`5` request envelope and bounded request
profile are valid. It performs no target lookup, same-author authorization,
address cutoff evaluation, replacement or suppression decision, store
-mutation, or deletion-request immunity evaluation. The event-contract registry
-v7 classifies `radroots.social.deletion_request.v1` as `TypedOnly` for
-authoring and `AdmissionOnly` for matching. Generic kind-`5` signing and client
-publication fail before signer access.
+mutation, or deletion-request immunity evaluation.
+
+The separate pure evaluator accepts a signature-verified candidate and
+admitted deletion requests. It never mutates an event or store. Kind `5` is
+immune; every other suppression requires equal request and candidate authors.
+An exact `e` target is time-independent. An exact canonical `a` target applies
+through the inclusive maximum qualifying request timestamp, so a later
+replacement remains visible. Advisory `k` values are ignored. The decision and
+its canonical evidence are independent of request order and repeated
+qualifying inputs.
+
+The event-contract registry v7 classifies
+`radroots.social.deletion_request.v1` as `TypedOnly` for authoring and
+`AdmissionOnly` for matching. Generic kind-`5` signing and client publication
+fail before signer access.
The complete operation surface is exactly
`social.deletion_request.build_authored_draft`,
-`social.deletion_request.project_verified_event`, and
-`social.deletion_request.verify_and_admit_event`. Content is limited to 131072
+`social.deletion_request.project_verified_event`,
+`social.deletion_request.verify_and_admit_event`, and
+`social.deletion_request.evaluate_suppression`. Content is limited to 131072
UTF-8 bytes, a request to 1024 tags, all tags to 4096 elements including names,
each element to 4096 UTF-8 bytes, aggregate tag bytes to 131072, and compact
-signed wire JSON to 262144 bytes. The canonical fixed 80-case corpus is
+signed wire JSON to 262144 bytes. The canonical fixed 80-case request corpus is
`contracts/conformance/vectors/deletion/verified_profile.v1.json`.
+Pure effect evaluation has the separate canonical
+`contracts/conformance/vectors/deletion/suppression.v1.json` corpus and does
+not weaken or add effect fields to the request corpus.
## Coverage matrix
@@ -234,7 +249,7 @@ signed wire JSON to 262144 bytes. The canonical fixed 80-case corpus is
| post | 1 | RadrootsAuthoredUpdate / RadrootsAuthoredPhotoUpdate / RadrootsAuthoredAsk / RadrootsInboundPostProjection | events.post.publish, events.post.list, events.post.get | ordinary kind-1 reads remain generic; exact root-card subtypes require verified admission; any `e` tag produces a thread-excluded candidate without a Reply claim |
| reply | 1 | RadrootsAuthoredNip10Reply / RadrootsInboundNip10ReplyProjection / RadrootsAdmittedNip10ReplyEvent / RadrootsNostrNip10ReplyEventBuilder | social.reply.build_authored_draft, social.reply.project_verified_event, social.reply.verify_and_admit_event | strict marked direct/nested NIP-10 authoring; verified marked or positional inbound admission with advisory-metadata diagnostics; never a root card; target existence, kind, author, and relay availability are not proven |
| comment | 1111 | RadrootsAuthoredNip22Comment / RadrootsInboundNip22CommentProjection / RadrootsAdmittedNip22CommentEvent / RadrootsNostrNip22CommentEventBuilder | social.comment.build_authored_draft, social.comment.project_verified_event, social.comment.verify_and_admit_event | strict NIP-22 event/address roots limited to kinds `30402`, `31922`, and `31923`; tolerant verified projection; registry-v7 typed-only authoring and admission-only matching |
-| deletion_request | 5 | RadrootsAuthoredNip09DeletionRequest / RadrootsInboundNip09DeletionProjection / RadrootsAdmittedNip09DeletionRequestEvent / RadrootsNostrNip09DeletionRequestEventBuilder | social.deletion_request.build_authored_draft, social.deletion_request.project_verified_event, social.deletion_request.verify_and_admit_event | effect-free NIP-09 request authoring and verified projection; event and replaceable/addressable targets; advisory kind diagnostics; registry-v7 typed-only authoring and admission-only matching |
+| deletion_request | 5 | RadrootsAuthoredNip09DeletionRequest / RadrootsInboundNip09DeletionProjection / RadrootsAdmittedNip09DeletionRequestEvent / RadrootsNip09SuppressionDecision / RadrootsNostrNip09DeletionRequestEventBuilder | social.deletion_request.build_authored_draft, social.deletion_request.project_verified_event, social.deletion_request.verify_and_admit_event, social.deletion_request.evaluate_suppression | effect-free NIP-09 request authoring and verified projection plus pure immutable suppression evaluation; same-author direct event targets and inclusive address cutoffs; kind-5 immunity; advisory kinds ignored; registry-v7 typed-only authoring and admission-only matching |
| reaction | 7 | RadrootsReaction | events.reaction.publish, events.reaction.list, events.reaction.get | requires event, pubkey, or address tags |
| repost | 6 | RadrootsRepost | events.repost.publish, events.repost.list, events.repost.get | NIP-18 kind-1 repost surface |
| generic_repost | 16 | RadrootsGenericRepost | events.generic_repost.publish, events.generic_repost.list, events.generic_repost.get | NIP-18 generic repost surface |
diff --git a/contracts/events/social-events.md b/contracts/events/social-events.md
@@ -36,11 +36,12 @@ authoring and admission profile are separate contract layers.
The repository implements strict authored and verified-projected kind `1` root-post profiles, a
separate strict-authored and tolerant-inbound kind `1` NIP-10 Reply profile, a strict-authored and
tolerant verified-inbound kind `1111` NIP-22 Comment profile, a strict-authored and tolerant
-verified-inbound kind `5` NIP-09 deletion-request profile, kind `7` `RadrootsReaction`, generic
-`RadrootsList` entries, operational listing records through `RadrootsOperationalListing`, the raw
-kind-`30402` profile partition and validated FoodAvailability authored, verified-admission, and
-revision contract, articles, generic public file metadata, calendar date events, calendar time
-events, reposts, generic reposts, calendar collections, RSVP events, and reports.
+verified-inbound kind `5` NIP-09 deletion-request profile with pure deterministic suppression
+evaluation, kind `7` `RadrootsReaction`, generic `RadrootsList` entries, operational listing
+records through `RadrootsOperationalListing`, the raw kind-`30402` profile partition and validated
+FoodAvailability authored, verified-admission, and revision contract, articles, generic public
+file metadata, calendar date events, calendar time events, reposts, generic reposts, calendar
+collections, RSVP events, and reports.
The closeout contract requires:
@@ -54,8 +55,8 @@ The closeout contract requires:
`e_prev` authority
- strict NIP-09 `RadrootsAuthoredNip09DeletionRequest`,
`RadrootsInboundNip09DeletionProjection`, and
- `RadrootsAdmittedNip09DeletionRequestEvent` request behavior without
- deletion-effect authority
+ `RadrootsAdmittedNip09DeletionRequestEvent` request behavior plus a separate
+ pure `RadrootsNip09SuppressionDecision` evaluator without storage authority
- strict NIP-25 `RadrootsReaction` behavior where empty content is a valid like
- explicit optional `published_at` support for NIP-99 classified-listing parity
- NIP-65 relay-list validation evidence through `RadrootsList`
@@ -78,7 +79,8 @@ The MVP public social substrate includes:
- strict `RadrootsAuthoredNip09DeletionRequest` publication plus
`RadrootsInboundNip09DeletionProjection` and
`RadrootsAdmittedNip09DeletionRequestEvent` for effect-free kind-`5` NIP-09
- event and address deletion requests
+ event and address deletion requests, followed by pure deterministic
+ suppression evaluation over verified targets and admitted requests
- `RadrootsArticle` for NIP-23 kind `30023` long-form content
- generic public `RadrootsFileMetadata` for NIP-94 kind `1063`
- strict authored `RadrootsAuthoredCalendarDateEvent`, tolerant
@@ -362,12 +364,27 @@ envelope whose NIP-01 id and Schnorr signature were verified. Admission proves
only a valid bounded deletion request. It does not look up a target, establish
same-author authorization, calculate an address cutoff, replace or suppress an
event, mutate a store, or decide whether another deletion request is immune.
-Those are downstream evaluator and storage responsibilities.
+Those remain separate evaluator and storage responsibilities.
+
+`evaluate_nip09_suppression` is the pure evaluator boundary. It accepts one
+signature-verified candidate event and a set of admitted deletion requests,
+never mutates either input, reads no clock or store, and returns an explicit
+decision with canonically ordered evidence. A kind-`5` candidate is always
+immune. Every other candidate can be suppressed only by a request from the
+same author. An `e` target is time-independent: an exact event-id match
+qualifies even when the request predates the candidate. An `a` target must
+match the candidate's canonical replaceable or addressable coordinate and
+suppresses revisions whose `created_at` is less than or equal to the inclusive
+maximum cutoff among qualifying requests. A later replacement remains
+unsuppressed. Advisory `k` tags never participate in authorization or
+suppression. Request ordering and repeated qualifying inputs cannot change the
+decision.
The complete public operation namespace is exactly
`social.deletion_request.build_authored_draft`,
-`social.deletion_request.project_verified_event`, and
-`social.deletion_request.verify_and_admit_event`. Contract
+`social.deletion_request.project_verified_event`,
+`social.deletion_request.verify_and_admit_event`, and
+`social.deletion_request.evaluate_suppression`. Contract
`radroots.social.deletion_request.v1` is registry-v7 `TypedOnly` for authoring
and `AdmissionOnly` for unsigned matching. Generic kind-`5` signing and client
publication are reserved before signer access.
@@ -383,6 +400,14 @@ contains no secret or private key, nsec, seed, generator, mutation, base, or
boundary-expansion recipe, and expected projections expose no authorization,
suppression, or store-mutation effect.
+Suppression behavior is governed separately by
+`contracts/conformance/vectors/deletion/suppression.v1.json` and its
+byte-identical packaged mirror. Its fixed inputs exercise direct event targets,
+inclusive address cutoffs, later replacements, requests that predate their
+targets, kind-`5` immunity, unrelated authors, mixed qualifying requests, and
+input-order independence. The evaluator produces evidence only; neither
+fixture nor operation authorizes a store mutation.
+
`RadrootsReaction` uses strict NIP-25 semantics. Empty content, `+`, `-`, emoji, and custom reaction
content are valid when the target tags are valid. Missing targets remain invalid.
diff --git a/contracts/operations.toml b/contracts/operations.toml
@@ -162,6 +162,11 @@ public = [
"RadrootsNip09DeletionProjectionError",
"RadrootsAdmittedNip09DeletionRequestEvent",
"RadrootsNip09DeletionAdmissionError",
+ "RadrootsNip09SuppressionOutcome",
+ "RadrootsNip09SuppressionReason",
+ "RadrootsNip09EventReferenceEvidence",
+ "RadrootsNip09AddressReferenceEvidence",
+ "RadrootsNip09SuppressionDecision",
"RadrootsReaction",
"RadrootsArticle",
"RadrootsFileMetadata",
@@ -1243,6 +1248,36 @@ case_kinds = [
"social.deletion_request.verify_and_admit_event.invalid",
]
+[operations.social_deletion_request_evaluate_suppression]
+domain = "social"
+id = "social.deletion_request.evaluate_suppression"
+stability = "beta"
+inputs = [
+ "RadrootsSignatureVerifiedEvent",
+ "RadrootsAdmittedNip09DeletionRequestEvent[]",
+]
+outputs = ["RadrootsNip09SuppressionDecision"]
+error_class = "validation_error"
+deterministic = true
+signing = "none"
+transport = "none"
+
+[operations.social_deletion_request_evaluate_suppression.implementation]
+rust_modules = ["crates/event_codec/src/deletion/evaluator.rs"]
+rust_types = [
+ "radroots_event_codec::deletion::admission::RadrootsAdmittedNip09DeletionRequestEvent",
+ "radroots_event_codec::deletion::evaluator::RadrootsNip09AddressReferenceEvidence",
+ "radroots_event_codec::deletion::evaluator::RadrootsNip09EventReferenceEvidence",
+ "radroots_event_codec::deletion::evaluator::RadrootsNip09SuppressionDecision",
+ "radroots_event_codec::deletion::evaluator::RadrootsNip09SuppressionOutcome",
+ "radroots_event_codec::deletion::evaluator::RadrootsNip09SuppressionReason",
+ "radroots_event_codec::verification::RadrootsSignatureVerifiedEvent",
+]
+
+[operations.social_deletion_request_evaluate_suppression.conformance]
+vector = "contracts/conformance/vectors/deletion/suppression.v1.json"
+case_kinds = ["social.deletion_request.evaluate_suppression.valid"]
+
[operations.social_reaction_build_tags]
domain = "social"
id = "social.reaction.build_tags"
diff --git a/contracts/releases/1.0.0-alpha.1.toml b/contracts/releases/1.0.0-alpha.1.toml
@@ -253,3 +253,14 @@ semver_impacts = [
"change_exported_algorithm_behavior",
]
summary = "Add strict effect-free NIP-09 deletion-request authoring, verified tolerant projection and admission, sealed publication, exact operation authority, and a fixed self-contained conformance corpus while advancing the event registry to version 7 and reserving generic kind-5 authoring."
+
+[[changes]]
+id = "pure-nip09-suppression-evaluator"
+classification = "feature"
+semver_impacts = [
+ "add_exported_type",
+ "add_exported_function",
+ "add_enum_variant",
+ "add_conformance_vector",
+]
+summary = "Add a pure deterministic NIP-09 evaluator that returns canonical suppression decisions and evidence for same-author event and inclusive address targets while preserving immutable events, kind-5 immunity, advisory-kind irrelevance, and storage ownership boundaries."
diff --git a/crates/event_codec/README b/crates/event_codec/README
@@ -109,7 +109,8 @@ valid shapes, diagnostics, exact and over-limit budgets, stable error
precedence, and NIP-01 admission without secret keys, mutation recipes, or
ambient state.
-The Deletion codec implements the request-only boundary of
+The Deletion codec implements the request and pure suppression-evaluation
+boundaries of
[NIP-09](https://github.com/nostr-protocol/nips/blob/bdfa7e62ef87fcfcb992b1a27aee49d36b0b4f91/09.md).
`authored_nip09_deletion_request_to_wire_parts` emits canonical two-element
`e` targets, then `a` targets, then the complete unique ascending set of `k`
@@ -125,10 +126,23 @@ this boundary.
`verify_and_admit_nip09_deletion_request_event` binds the projection to the
verified request envelope. It does not authorize or apply a deletion, look up
a target, compare authors, compute an address cutoff, suppress an event, or
-mutate storage. The exact operation namespace is
+mutate storage.
+
+`evaluate_nip09_suppression` is a separate pure operation over one
+`RadrootsSignatureVerifiedEvent` candidate and admitted deletion requests. It
+returns an explicit `RadrootsNip09SuppressionDecision` with canonical evidence
+without changing any input or touching a store. Kind `5` is immune. Other
+candidates require the request author to equal the candidate author. An exact
+event-id target applies independently of timestamps; a canonical address
+target applies through the inclusive maximum qualifying request timestamp, so
+a later replacement is not suppressed. Advisory `k` values do not affect the
+result, and request order cannot affect the decision.
+
+The exact operation namespace is
`social.deletion_request.build_authored_draft`,
-`social.deletion_request.project_verified_event`, and
-`social.deletion_request.verify_and_admit_event`. Registry v7 makes
+`social.deletion_request.project_verified_event`,
+`social.deletion_request.verify_and_admit_event`, and
+`social.deletion_request.evaluate_suppression`. Registry v7 makes
`radroots.social.deletion_request.v1` `TypedOnly` for authoring and
`AdmissionOnly` for matching, with generic kind-`5` authoring and publication
reserved.
@@ -141,6 +155,13 @@ cases cover canonical authoring, tolerant verified projection, diagnostics,
exact and over-limit resource budgets, precedence, and admission without
shipping keys, seeds, generators, or mutation recipes.
+The separate
+`contracts/conformance/vectors/deletion/suppression.v1.json` corpus and its
+byte-identical packaged fixture execute the evaluator's same-author, direct
+event, inclusive address-cutoff, later-replacement, pre-target request,
+kind-`5` immunity, and deterministic evidence behavior. These vectors govern a
+decision value only and claim no persistent store effect.
+
The FoodAvailability codec owns four boundaries for the focused
`radroots.food.availability.v1` kind-`30402` profile. Strict details plus
`created_at` produce bounded unsigned wire parts whose tag order is exactly
diff --git a/crates/event_codec/src/deletion/evaluator.rs b/crates/event_codec/src/deletion/evaluator.rs
@@ -0,0 +1,754 @@
+#[cfg(not(feature = "std"))]
+use alloc::format;
+
+use radroots_event::{
+ ids::{RadrootsEventId, RadrootsNip01Coordinate},
+ kinds::KIND_DELETION_REQUEST,
+};
+
+use crate::{
+ deletion::admission::RadrootsAdmittedNip09DeletionRequestEvent,
+ verification::RadrootsSignatureVerifiedEvent,
+};
+
+/// Whether a verified event remains visible after NIP-09 evaluation.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum RadrootsNip09SuppressionOutcome {
+ Visible,
+ Suppressed,
+}
+
+impl RadrootsNip09SuppressionOutcome {
+ pub const fn code(self) -> &'static str {
+ match self {
+ Self::Visible => "visible",
+ Self::Suppressed => "suppressed",
+ }
+ }
+}
+
+/// The stable explanation for a NIP-09 suppression outcome.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum RadrootsNip09SuppressionReason {
+ DeletionRequestImmune,
+ NoAuthorizedReference,
+ RequestAuthorMismatch,
+ AddressCutoffPrecedesTarget,
+ EventIdReference,
+ AddressReferenceAtOrBeforeCutoff,
+ EventIdAndAddressReference,
+}
+
+impl RadrootsNip09SuppressionReason {
+ pub const fn code(self) -> &'static str {
+ match self {
+ Self::DeletionRequestImmune => "deletion_request_immune",
+ Self::NoAuthorizedReference => "deletion_no_authorized_reference",
+ Self::RequestAuthorMismatch => "deletion_request_author_mismatch",
+ Self::AddressCutoffPrecedesTarget => "deletion_address_cutoff_precedes_target",
+ Self::EventIdReference => "deletion_event_id_reference",
+ Self::AddressReferenceAtOrBeforeCutoff => "deletion_address_reference",
+ Self::EventIdAndAddressReference => "deletion_event_id_and_address_reference",
+ }
+ }
+}
+
+/// Canonical evidence for an authorized exact event-id reference.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct RadrootsNip09EventReferenceEvidence {
+ request_id: RadrootsEventId,
+}
+
+impl RadrootsNip09EventReferenceEvidence {
+ pub const fn request_id(&self) -> &RadrootsEventId {
+ &self.request_id
+ }
+}
+
+/// Canonical evidence for authorized address references.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct RadrootsNip09AddressReferenceEvidence {
+ coordinate: RadrootsNip01Coordinate,
+ inclusive_cutoff: u64,
+ request_id: RadrootsEventId,
+}
+
+impl RadrootsNip09AddressReferenceEvidence {
+ pub const fn coordinate(&self) -> &RadrootsNip01Coordinate {
+ &self.coordinate
+ }
+
+ pub const fn inclusive_cutoff(&self) -> u64 {
+ self.inclusive_cutoff
+ }
+
+ pub const fn request_id(&self) -> &RadrootsEventId {
+ &self.request_id
+ }
+}
+
+/// A pure NIP-09 visibility decision with canonical supporting evidence.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct RadrootsNip09SuppressionDecision {
+ outcome: RadrootsNip09SuppressionOutcome,
+ reason: RadrootsNip09SuppressionReason,
+ event_reference: Option<RadrootsNip09EventReferenceEvidence>,
+ address_reference: Option<RadrootsNip09AddressReferenceEvidence>,
+}
+
+impl RadrootsNip09SuppressionDecision {
+ pub const fn outcome(&self) -> RadrootsNip09SuppressionOutcome {
+ self.outcome
+ }
+
+ pub const fn reason(&self) -> RadrootsNip09SuppressionReason {
+ self.reason
+ }
+
+ pub const fn event_reference(&self) -> Option<&RadrootsNip09EventReferenceEvidence> {
+ self.event_reference.as_ref()
+ }
+
+ pub const fn address_reference(&self) -> Option<&RadrootsNip09AddressReferenceEvidence> {
+ self.address_reference.as_ref()
+ }
+}
+
+/// Evaluates deterministic NIP-09 suppression without mutating stored events.
+pub fn evaluate_nip09_suppression(
+ target: &RadrootsSignatureVerifiedEvent,
+ requests: &[RadrootsAdmittedNip09DeletionRequestEvent],
+) -> RadrootsNip09SuppressionDecision {
+ let target_event = target.event();
+ if target_event.kind_u32() == KIND_DELETION_REQUEST {
+ return decision(
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::DeletionRequestImmune,
+ None,
+ None,
+ );
+ }
+
+ let target_coordinate = nip01_coordinate(target);
+ let mut event_reference = None;
+ let mut address_reference = None;
+ let mut has_unauthorized_reference = false;
+
+ for request in requests {
+ let request_event = request.event();
+ let projection = request.projection();
+ let event_matches = projection
+ .event_targets()
+ .iter()
+ .any(|reference| reference.event_id() == target_event.id());
+ let address_match = target_coordinate.as_ref().filter(|coordinate| {
+ projection
+ .address_targets()
+ .iter()
+ .any(|reference| reference.coordinate() == *coordinate)
+ });
+ if !event_matches && address_match.is_none() {
+ continue;
+ }
+ if request_event.author() != target_event.author() {
+ has_unauthorized_reference = true;
+ continue;
+ }
+
+ if event_matches
+ && event_reference.as_ref().is_none_or(
+ |current: &RadrootsNip09EventReferenceEvidence| {
+ request_event.id() < current.request_id()
+ },
+ )
+ {
+ event_reference = Some(RadrootsNip09EventReferenceEvidence {
+ request_id: request_event.id().clone(),
+ });
+ }
+ if let Some(coordinate) = address_match {
+ let inclusive_cutoff = request_event.created_at_u64();
+ if address_reference.as_ref().is_none_or(
+ |current: &RadrootsNip09AddressReferenceEvidence| {
+ inclusive_cutoff > current.inclusive_cutoff()
+ || (inclusive_cutoff == current.inclusive_cutoff()
+ && request_event.id() < current.request_id())
+ },
+ ) {
+ address_reference = Some(RadrootsNip09AddressReferenceEvidence {
+ coordinate: coordinate.clone(),
+ inclusive_cutoff,
+ request_id: request_event.id().clone(),
+ });
+ }
+ }
+ }
+
+ let address_applies = address_reference
+ .as_ref()
+ .is_some_and(|reference| target_event.created_at_u64() <= reference.inclusive_cutoff());
+ let (outcome, reason) = match (event_reference.is_some(), address_applies) {
+ (true, true) => (
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::EventIdAndAddressReference,
+ ),
+ (true, false) => (
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::EventIdReference,
+ ),
+ (false, true) => (
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff,
+ ),
+ (false, false) if address_reference.is_some() => (
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::AddressCutoffPrecedesTarget,
+ ),
+ (false, false) if has_unauthorized_reference => (
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::RequestAuthorMismatch,
+ ),
+ (false, false) => (
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::NoAuthorizedReference,
+ ),
+ };
+
+ decision(outcome, reason, event_reference, address_reference)
+}
+
+fn nip01_coordinate(target: &RadrootsSignatureVerifiedEvent) -> Option<RadrootsNip01Coordinate> {
+ let event = target.event();
+ let kind = event.kind_u32();
+ let identifier = if matches!(kind, 0 | 3) || (10_000..=19_999).contains(&kind) {
+ ""
+ } else if (30_000..=39_999).contains(&kind) {
+ event
+ .tag_slices()
+ .iter()
+ .find(|tag| tag.as_slice().first().is_some_and(|name| name == "d"))?
+ .as_slice()
+ .get(1)?
+ .as_str()
+ } else {
+ return None;
+ };
+ RadrootsNip01Coordinate::parse(format!("{kind}:{}:{identifier}", event.author().as_str())).ok()
+}
+
+const fn decision(
+ outcome: RadrootsNip09SuppressionOutcome,
+ reason: RadrootsNip09SuppressionReason,
+ event_reference: Option<RadrootsNip09EventReferenceEvidence>,
+ address_reference: Option<RadrootsNip09AddressReferenceEvidence>,
+) -> RadrootsNip09SuppressionDecision {
+ RadrootsNip09SuppressionDecision {
+ outcome,
+ reason,
+ event_reference,
+ address_reference,
+ }
+}
+
+#[cfg(all(test, feature = "nostr"))]
+mod tests {
+ use super::*;
+
+ use nostr::{Keys, SECP256K1, secp256k1::Message};
+ use radroots_event::{
+ RadrootsEventEnvelope, RadrootsEventEnvelopeParts, wire::compute_canonical_nip01_event_id,
+ };
+
+ use crate::{
+ deletion::admission::verify_and_admit_nip09_deletion_request_event,
+ test_fixtures::{FIXTURE_ALICE_SECRET_KEY_HEX, FIXTURE_BOB_SECRET_KEY_HEX},
+ verification::verify_nip01_event,
+ };
+
+ const TARGET_TIME: u64 = 1_800_100_100;
+
+ #[test]
+ fn nip09_evaluator_codes_and_opaque_evidence_are_stable() {
+ let outcome_codes = [
+ (RadrootsNip09SuppressionOutcome::Visible, "visible"),
+ (RadrootsNip09SuppressionOutcome::Suppressed, "suppressed"),
+ ];
+ for (outcome, code) in outcome_codes {
+ assert_eq!(outcome.code(), code);
+ }
+
+ let reason_codes = [
+ (
+ RadrootsNip09SuppressionReason::DeletionRequestImmune,
+ "deletion_request_immune",
+ ),
+ (
+ RadrootsNip09SuppressionReason::NoAuthorizedReference,
+ "deletion_no_authorized_reference",
+ ),
+ (
+ RadrootsNip09SuppressionReason::RequestAuthorMismatch,
+ "deletion_request_author_mismatch",
+ ),
+ (
+ RadrootsNip09SuppressionReason::AddressCutoffPrecedesTarget,
+ "deletion_address_cutoff_precedes_target",
+ ),
+ (
+ RadrootsNip09SuppressionReason::EventIdReference,
+ "deletion_event_id_reference",
+ ),
+ (
+ RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff,
+ "deletion_address_reference",
+ ),
+ (
+ RadrootsNip09SuppressionReason::EventIdAndAddressReference,
+ "deletion_event_id_and_address_reference",
+ ),
+ ];
+ for (reason, code) in reason_codes {
+ assert_eq!(reason.code(), code);
+ }
+ }
+
+ #[test]
+ fn nip09_evaluator_deletion_requests_are_immune() {
+ let target = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ vec![event_reference("a")],
+ "target",
+ );
+ let request = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME + 1,
+ vec![event_reference(target.event().id_str())],
+ "attempt",
+ );
+
+ let decision = evaluate_nip09_suppression(target.verified_event(), &[request]);
+
+ assert_decision(
+ &decision,
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::DeletionRequestImmune,
+ );
+ assert!(decision.event_reference().is_none());
+ assert!(decision.address_reference().is_none());
+ }
+
+ #[test]
+ fn nip09_evaluator_exact_event_reference_is_time_independent() {
+ let target = verified_event(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ 1,
+ Vec::new(),
+ "target",
+ );
+ let request = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME - 1,
+ vec![event_reference(target.event().id_str())],
+ "exact",
+ );
+ let request_id = request.event().id().clone();
+
+ let decision = evaluate_nip09_suppression(&target, &[request]);
+
+ assert_decision(
+ &decision,
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::EventIdReference,
+ );
+ assert_eq!(
+ decision
+ .event_reference()
+ .expect("event evidence")
+ .request_id(),
+ &request_id
+ );
+ assert!(decision.address_reference().is_none());
+ }
+
+ #[test]
+ fn nip09_evaluator_address_cutoff_is_inclusive_and_later_replacement_is_visible() {
+ let at_cutoff = verified_event(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ 30_402,
+ vec![d_tag("produce")],
+ "at cutoff",
+ );
+ let coordinate = coordinate_for(&at_cutoff, "produce");
+ let request = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ vec![address_reference(coordinate.as_str())],
+ "address",
+ );
+ let request_id = request.event().id().clone();
+
+ let suppressed = evaluate_nip09_suppression(&at_cutoff, core::slice::from_ref(&request));
+ assert_decision(
+ &suppressed,
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff,
+ );
+ let evidence = suppressed.address_reference().expect("address evidence");
+ assert_eq!(evidence.coordinate(), &coordinate);
+ assert_eq!(evidence.inclusive_cutoff(), TARGET_TIME);
+ assert_eq!(evidence.request_id(), &request_id);
+
+ let later = verified_event(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME + 1,
+ 30_402,
+ vec![d_tag("produce")],
+ "later",
+ );
+ let visible = evaluate_nip09_suppression(&later, &[request]);
+ assert_decision(
+ &visible,
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::AddressCutoffPrecedesTarget,
+ );
+ assert_eq!(
+ visible
+ .address_reference()
+ .expect("stale address evidence")
+ .inclusive_cutoff(),
+ TARGET_TIME
+ );
+ }
+
+ #[test]
+ fn nip09_evaluator_author_mismatch_and_unrelated_requests_are_distinct() {
+ let target = verified_event(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ 1,
+ Vec::new(),
+ "target",
+ );
+ let mismatch = admitted_request(
+ FIXTURE_BOB_SECRET_KEY_HEX,
+ TARGET_TIME + 1,
+ vec![event_reference(target.event().id_str())],
+ "wrong author",
+ );
+ let unrelated = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME + 1,
+ vec![event_reference("f")],
+ "unrelated",
+ );
+
+ let mismatch_decision = evaluate_nip09_suppression(&target, &[mismatch]);
+ assert_decision(
+ &mismatch_decision,
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::RequestAuthorMismatch,
+ );
+
+ let unrelated_decision = evaluate_nip09_suppression(&target, &[unrelated]);
+ assert_decision(
+ &unrelated_decision,
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::NoAuthorizedReference,
+ );
+ }
+
+ #[test]
+ fn nip09_evaluator_authorized_stale_address_precedes_unauthorized_exact_event() {
+ let target = verified_event(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ 0,
+ Vec::new(),
+ "profile",
+ );
+ let coordinate = coordinate_for(&target, "");
+ let stale = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME - 1,
+ vec![address_reference(coordinate.as_str())],
+ "stale",
+ );
+ let mismatch = admitted_request(
+ FIXTURE_BOB_SECRET_KEY_HEX,
+ TARGET_TIME + 1,
+ vec![event_reference(target.event().id_str())],
+ "wrong author",
+ );
+
+ let decision = evaluate_nip09_suppression(&target, &[mismatch, stale]);
+
+ assert_decision(
+ &decision,
+ RadrootsNip09SuppressionOutcome::Visible,
+ RadrootsNip09SuppressionReason::AddressCutoffPrecedesTarget,
+ );
+ }
+
+ #[test]
+ fn nip09_evaluator_exact_event_dominates_stale_address_reference() {
+ let target = verified_event(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ 30_000,
+ vec![d_tag("harvest")],
+ "target",
+ );
+ let coordinate = coordinate_for(&target, "harvest");
+ let exact = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME - 2,
+ vec![event_reference(target.event().id_str())],
+ "exact",
+ );
+ let stale = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME - 1,
+ vec![address_reference(coordinate.as_str())],
+ "stale",
+ );
+
+ let decision = evaluate_nip09_suppression(&target, &[stale, exact]);
+
+ assert_decision(
+ &decision,
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::EventIdReference,
+ );
+ assert!(decision.event_reference().is_some());
+ assert!(decision.address_reference().is_some());
+ }
+
+ #[test]
+ fn nip09_evaluator_reduction_is_order_and_repeat_invariant() {
+ let target = verified_event(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ 39_999,
+ vec![d_tag("market"), d_tag("ignored")],
+ "target",
+ );
+ let coordinate = coordinate_for(&target, "market");
+ let exact_a = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME - 10,
+ vec![event_reference(target.event().id_str())],
+ "exact a",
+ );
+ let exact_b = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME + 20,
+ vec![event_reference(target.event().id_str())],
+ "exact b",
+ );
+ let address_a = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME + 20,
+ vec![address_reference(coordinate.as_str())],
+ "address a",
+ );
+ let address_b = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME + 20,
+ vec![address_reference(coordinate.as_str())],
+ "address b",
+ );
+ let address_older = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME + 10,
+ vec![address_reference(coordinate.as_str())],
+ "address older",
+ );
+ let lower_exact_id = core::cmp::min(exact_a.event().id(), exact_b.event().id()).clone();
+ let lower_address_id =
+ core::cmp::min(address_a.event().id(), address_b.event().id()).clone();
+
+ let forward = vec![
+ exact_a.clone(),
+ address_a.clone(),
+ exact_b.clone(),
+ address_b.clone(),
+ address_older.clone(),
+ ];
+ let repeated_reverse = vec![
+ address_b,
+ address_older,
+ exact_b,
+ address_a,
+ exact_a.clone(),
+ exact_a,
+ ];
+ let expected = evaluate_nip09_suppression(&target, &forward);
+ let actual = evaluate_nip09_suppression(&target, &repeated_reverse);
+
+ assert_eq!(actual, expected);
+ assert_decision(
+ &actual,
+ RadrootsNip09SuppressionOutcome::Suppressed,
+ RadrootsNip09SuppressionReason::EventIdAndAddressReference,
+ );
+ assert_eq!(
+ actual
+ .event_reference()
+ .expect("event evidence")
+ .request_id(),
+ &lower_exact_id
+ );
+ let address = actual.address_reference().expect("address evidence");
+ assert_eq!(address.coordinate(), &coordinate);
+ assert_eq!(address.inclusive_cutoff(), TARGET_TIME + 20);
+ assert_eq!(address.request_id(), &lower_address_id);
+ }
+
+ #[test]
+ fn nip09_evaluator_uses_generic_coordinates_and_rejects_malformed_first_d() {
+ for kind in [0, 3, 10_000, 19_999] {
+ let target = verified_event(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ kind,
+ Vec::new(),
+ "replaceable",
+ );
+ let coordinate = coordinate_for(&target, "");
+ let request = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ vec![address_reference(coordinate.as_str())],
+ "replaceable",
+ );
+ assert_eq!(
+ evaluate_nip09_suppression(&target, &[request]).reason(),
+ RadrootsNip09SuppressionReason::AddressReferenceAtOrBeforeCutoff
+ );
+ }
+
+ let target = verified_event(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ 30_000,
+ vec![vec!["d".to_string()], d_tag("later")],
+ "malformed",
+ );
+ let later_coordinate = coordinate_for(&target, "later");
+ let request = admitted_request(
+ FIXTURE_ALICE_SECRET_KEY_HEX,
+ TARGET_TIME,
+ vec![address_reference(later_coordinate.as_str())],
+ "must not match",
+ );
+ assert_eq!(
+ evaluate_nip09_suppression(&target, &[request]).reason(),
+ RadrootsNip09SuppressionReason::NoAuthorizedReference
+ );
+ }
+
+ fn assert_decision(
+ decision: &RadrootsNip09SuppressionDecision,
+ outcome: RadrootsNip09SuppressionOutcome,
+ reason: RadrootsNip09SuppressionReason,
+ ) {
+ assert_eq!(decision.outcome(), outcome);
+ assert_eq!(decision.reason(), reason);
+ }
+
+ fn event_reference(value: &str) -> Vec<String> {
+ vec!["e".to_string(), normalize_event_id(value)]
+ }
+
+ fn address_reference(value: &str) -> Vec<String> {
+ vec!["a".to_string(), value.to_string()]
+ }
+
+ fn d_tag(value: &str) -> Vec<String> {
+ vec!["d".to_string(), value.to_string()]
+ }
+
+ fn normalize_event_id(value: &str) -> String {
+ if value.len() == 1 {
+ value.repeat(64)
+ } else {
+ value.to_string()
+ }
+ }
+
+ fn coordinate_for(
+ target: &RadrootsSignatureVerifiedEvent,
+ identifier: &str,
+ ) -> RadrootsNip01Coordinate {
+ RadrootsNip01Coordinate::parse(format!(
+ "{}:{}:{identifier}",
+ target.event().kind_u32(),
+ target.event().author_str()
+ ))
+ .expect("target coordinate")
+ }
+
+ fn admitted_request(
+ secret_key_hex: &str,
+ created_at: u64,
+ tags: Vec<Vec<String>>,
+ content: &str,
+ ) -> RadrootsAdmittedNip09DeletionRequestEvent {
+ verify_and_admit_nip09_deletion_request_event(signed_event(
+ secret_key_hex,
+ created_at,
+ KIND_DELETION_REQUEST,
+ tags,
+ content,
+ ))
+ .expect("valid admitted deletion request")
+ }
+
+ fn verified_event(
+ secret_key_hex: &str,
+ created_at: u64,
+ kind: u32,
+ tags: Vec<Vec<String>>,
+ content: &str,
+ ) -> RadrootsSignatureVerifiedEvent {
+ verify_nip01_event(signed_event(
+ secret_key_hex,
+ created_at,
+ kind,
+ tags,
+ content,
+ ))
+ .expect("valid verified event")
+ }
+
+ fn signed_event(
+ secret_key_hex: &str,
+ created_at: u64,
+ kind: u32,
+ tags: Vec<Vec<String>>,
+ content: &str,
+ ) -> RadrootsEventEnvelope {
+ let keys = Keys::parse(secret_key_hex).expect("fixed fixture secret key must parse");
+ let author = keys.public_key().to_string();
+ let id =
+ compute_canonical_nip01_event_id(author.as_str(), created_at, kind, &tags, content)
+ .expect("canonical event id");
+ let nostr_id = nostr::EventId::from_hex(id.as_str()).expect("Nostr event id");
+ let message = Message::from_digest(nostr_id.to_bytes());
+ let signature = SECP256K1.sign_schnorr_no_aux_rand(&message, keys.key_pair(SECP256K1));
+
+ RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts {
+ id: id.into_string(),
+ author,
+ created_at,
+ kind,
+ tags,
+ content: content.to_string(),
+ sig: signature.to_string(),
+ })
+ .expect("valid event envelope")
+ }
+}
diff --git a/crates/event_codec/src/deletion/mod.rs b/crates/event_codec/src/deletion/mod.rs
@@ -1,3 +1,4 @@
pub mod admission;
pub mod authored;
+pub mod evaluator;
pub mod inbound;
diff --git a/crates/event_codec/tests/fixtures/deletion_suppression.v1.json b/crates/event_codec/tests/fixtures/deletion_suppression.v1.json
@@ -0,0 +1,609 @@
+{
+ "suite": "nip09_suppression_evaluator",
+ "contract_version": "1.0.0",
+ "vectors": [
+ {
+ "id": "nip09_suppress_no_requests_visible",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_no_authorized_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_same_author_event_reference",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"event reference a\",\"sig\":\"36ab6c25bcc0fa22da55a566121cf43210f90a8f0f9243736fd24ff57b4799ac09cf017ef3cd132464b728bb5ff87603535fbb1b566413ed5f34a60c1838a809\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_same_author_nonmatching_reference",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"9b3d3a1ca7f4a7019a17c86465cadfeaa262ebc76042d2a5a07971e09b36d14c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"120bd6d1a82eb11b24f05316f3254c0fa6cb87fcc909d7200a963e40207f5e77\"]],\"content\":\"other reference\",\"sig\":\"9765abc98be250a2b60b6f3aa33e695ff58e0aa5f5b2e927cbb878acdb25b645a8d1a9d03f6cb9de69448c4ec6bc7e6f2cc5276a3865ac0a4d4c8daf4b331a48\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_no_authorized_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_wrong_author_exact_event_reference",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"403981275c07a7c67d35552ee3dbc416c20b770d5485216ff2d1b18ad287588b\",\"pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"wrong author event\",\"sig\":\"e7f2e84135c979c0d3fa815d3b9082163152e37d55cf0f40e21e4df81baee81efe1365b0011da940b63efb7dc9d13fb7d84d990aee4a04e4dd582790ff205770\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_request_author_mismatch"
+ }
+ },
+ {
+ "id": "nip09_suppress_event_reference_predates_target",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"a75e12b2175feafcad0763545cbbf31c118915f60d97572bb68fafbcc533f6be\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100190,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"pre-target exact event\",\"sig\":\"c2fbe879659b5da522dbb400ac08174f522482a8077fa10c868c1a395598e2e40175d1f232964d897f84e9e710de8c9a2b0feff088cead33a5d1879a96fa1d7d\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "a75e12b2175feafcad0763545cbbf31c118915f60d97572bb68fafbcc533f6be"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_deletion_request_immune_event_reference",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"a8fb77cd006005bffaa04f49ba01560dccfc2ca9875d79bd0a7bdc3862553df5\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"a9bbfa0344854ced08a13230a3a84b2d3971f5bf08f1f6af25f1547eec307a89\"]],\"content\":\"delete a deletion\",\"sig\":\"52a7356d38a5a49c32094905a5b4ff5c4adb41146a6b046377541cd91cf44b2f0cdb7640aab556c8375fafc8460f6f3076731d12b9923cc15538d54dcdd47f6a\"}"
+ ],
+ "target_event_json": "{\"id\":\"a9bbfa0344854ced08a13230a3a84b2d3971f5bf08f1f6af25f1547eec307a89\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":5,\"tags\":[[\"e\",\"120bd6d1a82eb11b24f05316f3254c0fa6cb87fcc909d7200a963e40207f5e77\"]],\"content\":\"original deletion\",\"sig\":\"5f17a714f308ca33c2d5f1e4b52d2a56a406474ae0e6a1cd4482b3a3cf116fdab7cebe77e5a6cc18a2490f32292bcdf4df08ab8d3a2d507df4cd99c39e6fb1f7\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_request_immune"
+ }
+ },
+ {
+ "id": "nip09_suppress_deletion_request_immune_mixed_references",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"6cebea08ac79c820dac9936444a818dd8aa89dffb5b0b251b21edea266463860\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100211,\"kind\":5,\"tags\":[[\"e\",\"a9bbfa0344854ced08a13230a3a84b2d3971f5bf08f1f6af25f1547eec307a89\"],[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"mixed deletion target\",\"sig\":\"464b930b47df90bf5c9d6f0138c082d71c3fb84f49755168e690672cc1a47635c507518f876cc9aceff60765c3db9a8be1b774a3322b79dbe82bd45f3c06de8b\"}"
+ ],
+ "target_event_json": "{\"id\":\"a9bbfa0344854ced08a13230a3a84b2d3971f5bf08f1f6af25f1547eec307a89\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":5,\"tags\":[[\"e\",\"120bd6d1a82eb11b24f05316f3254c0fa6cb87fcc909d7200a963e40207f5e77\"]],\"content\":\"original deletion\",\"sig\":\"5f17a714f308ca33c2d5f1e4b52d2a56a406474ae0e6a1cd4482b3a3cf116fdab7cebe77e5a6cc18a2490f32292bcdf4df08ab8d3a2d507df4cd99c39e6fb1f7\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_request_immune"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_cutoff_before_target",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"1473dabefc767c7a2a16ce716e0cbcd1ea463299c6e1e4d1c805672669012bd2\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100199,\"kind\":5,\"tags\":[[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"address before\",\"sig\":\"7bdf964d33f45eaeb81fbc671ae994dfdcf61dcac6c37f70831be76148ff9e05a2a0abe673a8655a2d3acaffdfba5aaa719cc00a26b9f3938626839681e991dd\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100199,
+ "request_id": "1473dabefc767c7a2a16ce716e0cbcd1ea463299c6e1e4d1c805672669012bd2"
+ },
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_address_cutoff_precedes_target"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_cutoff_equal_target",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"3d75aa21ad472076c331419a3f2e73284ef8083f79d11f47b4c79e76604dc775\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":5,\"tags\":[[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"address equal\",\"sig\":\"aeda08b49690df9655012e964b69dca4c9f5f1545423378581875b8fb1ea27719ee0ba1c6c54a06c6938dec8b967379403c45d43e208b6746715f0cdf8c26e73\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100200,
+ "request_id": "3d75aa21ad472076c331419a3f2e73284ef8083f79d11f47b4c79e76604dc775"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_cutoff_after_target",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"086915e7b1c08ba14f5c8fabca8490b56b022b093ce21490d8f706df83c13e2c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100201,\"kind\":5,\"tags\":[[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"address after\",\"sig\":\"49dc8bd554da237d73ac266f893895df0509e4cf3b5eb372b393c42b8872bf1afb1d1922be622acbf6b811657d15beac4f6a2e69d24206b185db3135ed6cdb70\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100201,
+ "request_id": "086915e7b1c08ba14f5c8fabca8490b56b022b093ce21490d8f706df83c13e2c"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_wrong_kind",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"b24aa726605e907b6d2733e8e36e47f890e544583c976fa0347b054d706e74ec\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"3:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"wrong kind\",\"sig\":\"a03a7de08ba411613602fd0d8d04f70dbe81dd194bb7a556691595a2830a0334883cb0b45d9e6ddb253aed25de405da567c4c9f48a2064172ddef88959b7209c\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_no_authorized_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_wrong_identifier",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"8388ea59c1f08f5091b3332e172145ef09bc671e7ad71e44c198867a66578824\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"39999:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:orchard:east/plot\"]],\"content\":\"wrong identifier\",\"sig\":\"85f06ad83418d262150642d1b64eea745f410232c6589d7cde7fd63fd896fd66b644db93a275cbeceadebf0f4976ee9cdee38ef2e6f84c76364e9309a4c614bb\"}"
+ ],
+ "target_event_json": "{\"id\":\"198aeb65fa856b3eb7b5a7fe36fa90da70fbdbb1cb88b31a3893d07c9437c796\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":39999,\"tags\":[[\"d\",\"orchard:west/plot\"]],\"content\":\"opaque addressable\",\"sig\":\"cc3db13caa8e407e842d5a977f720624a17364bf617179aa5a9318e7ab111818b82d20799e074092522fa49c8b709e4b8af1877a0599c63797f81a71689b9638\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_no_authorized_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_address_wrong_pubkey",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"39ab140373199234d51f13034f0383a6d7a492d49303cf0b5c08f31d876674f6\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"0:e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af:\"]],\"content\":\"wrong pubkey\",\"sig\":\"efc60d415f344ce7103ad9179a6642c5823caf2e4b44f642276130f8be7e9400d882ce1299bdf898bdbfc01efd105f525c2a3dc55dd0d56531b74ed173b171a6\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_no_authorized_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_wrong_author_address_reference",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"616960881e5bdaa21bff166269193e8503fc0d8f55507ec955a55421c34c46c5\",\"pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"wrong author address\",\"sig\":\"515d51aca57cecaeb94111daf508979c6b78523ef4005ffed9966a39beeaea26c01b727482f82ddea88db8f3333f4e00dd6fa1ad4eddd0ec43ba213bb44f96f5\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_request_author_mismatch"
+ }
+ },
+ {
+ "id": "nip09_suppress_replaceable_kind_0",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"086915e7b1c08ba14f5c8fabca8490b56b022b093ce21490d8f706df83c13e2c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100201,\"kind\":5,\"tags\":[[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"address after\",\"sig\":\"49dc8bd554da237d73ac266f893895df0509e4cf3b5eb372b393c42b8872bf1afb1d1922be622acbf6b811657d15beac4f6a2e69d24206b185db3135ed6cdb70\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100201,
+ "request_id": "086915e7b1c08ba14f5c8fabca8490b56b022b093ce21490d8f706df83c13e2c"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_replaceable_kind_3",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"e10db54031bbff5da53d70d7bba7f6ee3ce4497152618cad9e2ea78567874169\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"3:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"replaceable kind 3\",\"sig\":\"816d45c35ebdef5495ec1060f52fad19ea88b4d093431e1ae194ca5e7380145950b2b03dea2dd701e13a4e99c0c6071bb2e45e59782ce3a0547ef8a4ff6dadab\"}"
+ ],
+ "target_event_json": "{\"id\":\"9b7a698850a451d198e792be95a10bb093ccfd65ebbdc8395d4282c930f281ae\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":3,\"tags\":[],\"content\":\"contacts\",\"sig\":\"30e46ce6dc57368992a86e2eace20470164de87aeecc08c9717231af4b75eb45e6945fc3b07be3cbf640a2b7fb75c9811903f94027bbe39374cc1a06155a4aea\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "3:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "e10db54031bbff5da53d70d7bba7f6ee3ce4497152618cad9e2ea78567874169"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_replaceable_kind_10000",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"e512278a22f532facc56739af02486b579a680b7a71ab0e064b86875049f79b1\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"10000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"replaceable kind 10000\",\"sig\":\"b814886b0f702015d37ed545e14771ae9c712ca57ad7e81437add6e109060d346b1695c7abb6b1810198d89af6fe0a50ee5eb54ed4676024652276932ea0d4de\"}"
+ ],
+ "target_event_json": "{\"id\":\"db73709e2333601fa97d8ade507c1fb046d37129d32b99e4ff24b316895399e1\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":10000,\"tags\":[],\"content\":\"replaceable lower\",\"sig\":\"8152e75fefbfa5e361402a70c4ee796d5cc6e11cf98ea447e2e7f53a8ad6b8618edea1dcdd8194deaf2304c5e53b442f56fc9e8809b062d0f6a1f07ea1caddb2\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "10000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "e512278a22f532facc56739af02486b579a680b7a71ab0e064b86875049f79b1"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_replaceable_kind_19999",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"9bafe3e4a5ea4bc2f6c2027c096bab9962949d0a172003a3177f7739dc1e55eb\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"19999:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"replaceable kind 19999\",\"sig\":\"8d8ecdcf165819bff0f173289a8ad13758b812f36a12b43de37f1cea468a893e89ae542121b6e3d3fb75b52a946ef2ac81dfad209350ece01b4548d4a04a61cc\"}"
+ ],
+ "target_event_json": "{\"id\":\"22ae552d598bd60490890db49e4738fc6d10376dc9fb9765fb319b3d3d72a472\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":19999,\"tags\":[],\"content\":\"replaceable upper\",\"sig\":\"16db6c2afd5eaf97143d0ecf1ab6535c2df08e1d61dffcf2feabea54dca1a42d3f11b1d82ac77bb60b4498469a25823c1adc6ae133a00ddf77c86ba6f6ccbb60\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "19999:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "9bafe3e4a5ea4bc2f6c2027c096bab9962949d0a172003a3177f7739dc1e55eb"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_addressable_kind_30000_empty_identifier",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"9900ec21cbddfe6598f64983cf52232ff3b59250fa41810dc8ca883a22c21d49\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"addressable kind 30000\",\"sig\":\"a8cee6e2fe394cf26ac783a57d275a8891f716cc8077a85e488fd02d04c9c821ce65e98543b022b1cf62733d2e7a0fbfdfaaec2c49bad68f761d49dfd09ad262\"}"
+ ],
+ "target_event_json": "{\"id\":\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable initial\",\"sig\":\"a60cffce8efd7b1a1372d6d0b175afc49bcd1e7a88b94f3e57ef212a4b7f9dbbb91c058c2af11209a20b2cd30d6ce74886fcf770c726dc8c3e20d0081e0ec1fa\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "9900ec21cbddfe6598f64983cf52232ff3b59250fa41810dc8ca883a22c21d49"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_addressable_kind_39999_opaque_identifier",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"9a308e463030f356e68abefba2bc9c766c766f509ec6809c8709ebe7da8d355e\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"a\",\"39999:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:orchard:west/plot\"]],\"content\":\"addressable kind 39999\",\"sig\":\"bd7b5f63aaf54a80283da21103556e235851b9e31e5b668808ceff8f6d1af303b4334b7855478b277bca7da1775a630b46671ca195afd1f920752a7cb391afb2\"}"
+ ],
+ "target_event_json": "{\"id\":\"198aeb65fa856b3eb7b5a7fe36fa90da70fbdbb1cb88b31a3893d07c9437c796\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":39999,\"tags\":[[\"d\",\"orchard:west/plot\"]],\"content\":\"opaque addressable\",\"sig\":\"cc3db13caa8e407e842d5a977f720624a17364bf617179aa5a9318e7ab111818b82d20799e074092522fa49c8b709e4b8af1877a0599c63797f81a71689b9638\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "39999:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:orchard:west/plot",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "9a308e463030f356e68abefba2bc9c766c766f509ec6809c8709ebe7da8d355e"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_combined_event_and_address_references",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"eec90d8cb7e493d68b197310454c1f5b92828af5973aa26ee8d5fb82e0f30434\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"e\",\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\"],[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"combined references\",\"sig\":\"3f8203c5d57289e9ebdc5c2e2ab7f487ee60e409d5d4667057eba0ad10153c59aa8236d37e7d177a693243f9c9896cf05f8afd971dfc358438a55cc3ff5ebfaf\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "eec90d8cb7e493d68b197310454c1f5b92828af5973aa26ee8d5fb82e0f30434"
+ },
+ "event_reference": {
+ "request_id": "eec90d8cb7e493d68b197310454c1f5b92828af5973aa26ee8d5fb82e0f30434"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_and_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_event_reference_with_stale_address",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"a0165c75548878cd6a959ab2a12e3826c1b118240c328cc0470d441b7306edf0\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100199,\"kind\":5,\"tags\":[[\"e\",\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\"],[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"exact event stale address\",\"sig\":\"512a96994d5f357abd9cd9c7cc7a00658ec4ee7cbe4bb6d83fbe26b22c75755f92c43f89b4ff8f12ce0c06e44fa1277c370aac1de4551543831a6640cb1496f4\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100199,
+ "request_id": "a0165c75548878cd6a959ab2a12e3826c1b118240c328cc0470d441b7306edf0"
+ },
+ "event_reference": {
+ "request_id": "a0165c75548878cd6a959ab2a12e3826c1b118240c328cc0470d441b7306edf0"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_kind_advisory_diagnostics_ignored",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"859689351e1457702ea9fb97e056185781bd779dc3889b42b9bed49936691ea9\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"],[\"k\"],[\"k\",\"bogus\"],[\"k\",\"1\"],[\"k\",\"1\"],[\"k\",\"2\"]],\"content\":\"ignored kind diagnostics\",\"sig\":\"5dfaa28b172be180313aee8e204b08a2a4a208f81ecdd1727f0d55bddfd57fbd62ac5cc0cbda5f17f8f4bd4bf758f77c0685723859d9269e0a2c7f39bde88354\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "859689351e1457702ea9fb97e056185781bd779dc3889b42b9bed49936691ea9"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_duplicate_raw_targets_deduplicated",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"ab329bb4dcb4e7b3b706f44b1212e8464d0c1a622e0f7d4cb5da62b5b7291ade\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100220,\"kind\":5,\"tags\":[[\"e\",\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\"],[\"e\",\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\"],[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"],[\"a\",\"0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"duplicate raw references\",\"sig\":\"7479c3b44811e401d3cf51140779db9b2b9c6b04194f643e01c4b5f2f3adfa54812ebb1f36d83cdc5d8dcc72eb88335fae281c9383135dbe7da41c0e5a1287fe\"}"
+ ],
+ "target_event_json": "{\"id\":\"8b1da693935771675c95b8a47c883f03edcc96060d4cbeaa07280e7b2b505a35\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":0,\"tags\":[],\"content\":\"profile zero\",\"sig\":\"b6c8075b9e14f9397b5c51a7ccc4fe83971a14144801c86153c45e2455dc82fa921f66af12f1d2e99fccab2608b22b8d4d6920e81f965073ac65762b065fa926\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "0:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100220,
+ "request_id": "ab329bb4dcb4e7b3b706f44b1212e8464d0c1a622e0f7d4cb5da62b5b7291ade"
+ },
+ "event_reference": {
+ "request_id": "ab329bb4dcb4e7b3b706f44b1212e8464d0c1a622e0f7d4cb5da62b5b7291ade"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_and_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_max_address_cutoff_forward_order",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"88979696e9bbc7762dd224aa4a7c95dbe19567e0d83667185472a4124105c7aa\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100230,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 230\",\"sig\":\"420f8775b6e5454e8fb69207f00b1c9d215e3f3f837e3433d1c939acba5bb7dcea64db45edcfbeb3c621696cf9c58ecb469e298a2377ca28c399a8e2c5dd97bd\"}",
+ "{\"id\":\"2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100240,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 240 a\",\"sig\":\"7562e4477358a0788f7ca4052944fb647235910504de38280b3737b0e43cb1628beb4a19312d83e93438a131d9ab7239193696bc61d70e09e370c98023c45cd2\"}"
+ ],
+ "target_event_json": "{\"id\":\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable initial\",\"sig\":\"a60cffce8efd7b1a1372d6d0b175afc49bcd1e7a88b94f3e57ef212a4b7f9dbbb91c058c2af11209a20b2cd30d6ce74886fcf770c726dc8c3e20d0081e0ec1fa\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100240,
+ "request_id": "2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_max_address_cutoff_reverse_order",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100240,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 240 a\",\"sig\":\"7562e4477358a0788f7ca4052944fb647235910504de38280b3737b0e43cb1628beb4a19312d83e93438a131d9ab7239193696bc61d70e09e370c98023c45cd2\"}",
+ "{\"id\":\"88979696e9bbc7762dd224aa4a7c95dbe19567e0d83667185472a4124105c7aa\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100230,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 230\",\"sig\":\"420f8775b6e5454e8fb69207f00b1c9d215e3f3f837e3433d1c939acba5bb7dcea64db45edcfbeb3c621696cf9c58ecb469e298a2377ca28c399a8e2c5dd97bd\"}"
+ ],
+ "target_event_json": "{\"id\":\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable initial\",\"sig\":\"a60cffce8efd7b1a1372d6d0b175afc49bcd1e7a88b94f3e57ef212a4b7f9dbbb91c058c2af11209a20b2cd30d6ce74886fcf770c726dc8c3e20d0081e0ec1fa\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100240,
+ "request_id": "2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_later_revision_survives_address_cutoff",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"88979696e9bbc7762dd224aa4a7c95dbe19567e0d83667185472a4124105c7aa\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100230,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 230\",\"sig\":\"420f8775b6e5454e8fb69207f00b1c9d215e3f3f837e3433d1c939acba5bb7dcea64db45edcfbeb3c621696cf9c58ecb469e298a2377ca28c399a8e2c5dd97bd\"}",
+ "{\"id\":\"2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100240,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 240 a\",\"sig\":\"7562e4477358a0788f7ca4052944fb647235910504de38280b3737b0e43cb1628beb4a19312d83e93438a131d9ab7239193696bc61d70e09e370c98023c45cd2\"}"
+ ],
+ "target_event_json": "{\"id\":\"d35339c16ecb0baff3149324c96c01983e4263278a1e1d6f58ad41f538bcd3ed\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100260,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable later\",\"sig\":\"55e1bffad1fa0553c1b67f27a15f226c3ea18ba2a2716f12a8ae46cdd007d2c3b2a6d36997521de8a70cc2914dceb1f0fe1664176e27c458069f7c04b9383ad4\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100240,
+ "request_id": "2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c"
+ },
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_address_cutoff_precedes_target"
+ }
+ },
+ {
+ "id": "nip09_suppress_equal_max_cutoff_uses_lowest_request_id",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100240,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 240 a\",\"sig\":\"7562e4477358a0788f7ca4052944fb647235910504de38280b3737b0e43cb1628beb4a19312d83e93438a131d9ab7239193696bc61d70e09e370c98023c45cd2\"}",
+ "{\"id\":\"5bb1267cbc05f8c9365cc6f73ab79cc800c85fdfbdbe4e3d6d319574937244f6\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100240,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"cutoff 240 b\",\"sig\":\"55d93ad2d0702eb9fdb8e019c5802b75ecef439951ab77c76b18bac5d09a2d465fc6e4618dcc43bb7bfb62690e9cae0d60c5dba99c8f3ab24f3231b4e45c8bdf\"}"
+ ],
+ "target_event_json": "{\"id\":\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable initial\",\"sig\":\"a60cffce8efd7b1a1372d6d0b175afc49bcd1e7a88b94f3e57ef212a4b7f9dbbb91c058c2af11209a20b2cd30d6ce74886fcf770c726dc8c3e20d0081e0ec1fa\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100240,
+ "request_id": "2ca4618370342696c7daedca89741bdea10f0b496bf24907c367cd549c36697c"
+ },
+ "event_reference": null,
+ "outcome": "suppressed",
+ "reason": "deletion_address_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_multiple_event_references_use_lowest_request_id",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"event reference a\",\"sig\":\"36ab6c25bcc0fa22da55a566121cf43210f90a8f0f9243736fd24ff57b4799ac09cf017ef3cd132464b728bb5ff87603535fbb1b566413ed5f34a60c1838a809\"}",
+ "{\"id\":\"2284746bed9c30d7d7829553a42fa5e53200bf8bd6160a99076ee97e9b3aa75b\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100211,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"event reference b\",\"sig\":\"920891d1e65e9b476e0a0810cde3583bd92586c3e07feb650727ade2aff5487c08f0c37fc669bfd3137082991fc1ac0e467987c3b8468a11107b5b4fcd59a3a5\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "2284746bed9c30d7d7829553a42fa5e53200bf8bd6160a99076ee97e9b3aa75b"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_repeated_request_is_idempotent",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"event reference a\",\"sig\":\"36ab6c25bcc0fa22da55a566121cf43210f90a8f0f9243736fd24ff57b4799ac09cf017ef3cd132464b728bb5ff87603535fbb1b566413ed5f34a60c1838a809\"}",
+ "{\"id\":\"fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100210,\"kind\":5,\"tags\":[[\"e\",\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\"]],\"content\":\"event reference a\",\"sig\":\"36ab6c25bcc0fa22da55a566121cf43210f90a8f0f9243736fd24ff57b4799ac09cf017ef3cd132464b728bb5ff87603535fbb1b566413ed5f34a60c1838a809\"}"
+ ],
+ "target_event_json": "{\"id\":\"18defc3fb2ba6d3f7d0edf19cf942f888c7403b2a3e36bf21a7efa88ddf4b235\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":1,\"tags\":[],\"content\":\"regular target\",\"sig\":\"0f8eaacba0e40a2cca3ce1ae0d9aba42266c2ccc4d7465f5d056d534ea7abfba4be8de36e2a3262cbda6a207554e8fd50cae2605df98586075e892db1ea7b724\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "fade0f4ddcd9f2f8b9c6508055ceb58c687bd8b99cdaf5f4b63cc13b16f75278"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ },
+ {
+ "id": "nip09_suppress_unauthorized_event_plus_stale_authorized_address",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"23f5fa17f6c32fbac2be7d3bbac04f772c8736610c508bb058d5176f07fb83cd\",\"pubkey\":\"e0266e3cfb0d2886f91c73f5f868f3b98273713e5fcd97c081663f5518a4b3af\",\"created_at\":1800100270,\"kind\":5,\"tags\":[[\"e\",\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\"]],\"content\":\"unauthorized exact addressable event\",\"sig\":\"554ecdb5a70ae3a5a57689a531635876d71e66a9f39deaff4384f1f3a983824c11566c8e007487ef664abdde03f248b18404254f958c5cf20ae2d9cb0c78e7b6\"}",
+ "{\"id\":\"0e2e9cb53f3c98e254fdbba41f6f87f470dceed991d2fee7c06db5957690b182\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100199,\"kind\":5,\"tags\":[[\"a\",\"30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:\"]],\"content\":\"stale authorized address\",\"sig\":\"2d3b1a3f296eb2558af31611426af71f705a503f6671f9528d6752214fbbbb2b7b47837b9acc7e3f69f1bd068058cad735ea48fbd6ff1f2a1f793eefad165907\"}"
+ ],
+ "target_event_json": "{\"id\":\"8cb1b211dc3b8bfbd7f457ffe8f4b067f0b065c2658126ee65a82f43ffb67c5a\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100200,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable initial\",\"sig\":\"a60cffce8efd7b1a1372d6d0b175afc49bcd1e7a88b94f3e57ef212a4b7f9dbbb91c058c2af11209a20b2cd30d6ce74886fcf770c726dc8c3e20d0081e0ec1fa\"}"
+ },
+ "expected": {
+ "address_reference": {
+ "coordinate": "30000:585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df:",
+ "inclusive_cutoff": 1800100199,
+ "request_id": "0e2e9cb53f3c98e254fdbba41f6f87f470dceed991d2fee7c06db5957690b182"
+ },
+ "event_reference": null,
+ "outcome": "visible",
+ "reason": "deletion_address_cutoff_precedes_target"
+ }
+ },
+ {
+ "id": "nip09_suppress_exact_event_deletes_later_replacement",
+ "kind": "social.deletion_request.evaluate_suppression.valid",
+ "input": {
+ "request_event_jsons": [
+ "{\"id\":\"5d0e1e5fbd9f7f8afc20b7b53b7387fb5bbeb4cfaa9ab715833efc3ed248f83d\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100270,\"kind\":5,\"tags\":[[\"e\",\"d35339c16ecb0baff3149324c96c01983e4263278a1e1d6f58ad41f538bcd3ed\"]],\"content\":\"exact later replacement\",\"sig\":\"c7beed981d08bb5be1b1938baaac92554bfe0133ac1de85c56c0205c4adcd3f52f2895819faa2d8c1733fdad6589c6fba9547135e5b580a230881621f5d083b8\"}"
+ ],
+ "target_event_json": "{\"id\":\"d35339c16ecb0baff3149324c96c01983e4263278a1e1d6f58ad41f538bcd3ed\",\"pubkey\":\"585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df\",\"created_at\":1800100260,\"kind\":30000,\"tags\":[[\"d\",\"\"]],\"content\":\"addressable later\",\"sig\":\"55e1bffad1fa0553c1b67f27a15f226c3ea18ba2a2716f12a8ae46cdd007d2c3b2a6d36997521de8a70cc2914dceb1f0fe1664176e27c458069f7c04b9383ad4\"}"
+ },
+ "expected": {
+ "address_reference": null,
+ "event_reference": {
+ "request_id": "5d0e1e5fbd9f7f8afc20b7b53b7387fb5bbeb4cfaa9ab715833efc3ed248f83d"
+ },
+ "outcome": "suppressed",
+ "reason": "deletion_event_id_reference"
+ }
+ }
+ ]
+}
diff --git a/crates/event_codec/tests/nip09_suppression_conformance.rs b/crates/event_codec/tests/nip09_suppression_conformance.rs
@@ -0,0 +1,587 @@
+#![cfg(all(feature = "serde_json", feature = "nostr"))]
+
+use std::{
+ borrow::Cow,
+ collections::{BTreeMap, BTreeSet},
+ fs,
+ path::Path,
+};
+
+use radroots_event::{RadrootsEventEnvelope, RadrootsEventEnvelopeParts};
+use radroots_event_codec::{
+ deletion::{
+ admission::{
+ RadrootsAdmittedNip09DeletionRequestEvent, admit_verified_nip09_deletion_request_event,
+ },
+ evaluator::{RadrootsNip09SuppressionDecision, evaluate_nip09_suppression},
+ },
+ verification::{RadrootsSignatureVerifiedEvent, verify_nip01_event},
+};
+use serde::{Deserialize, Serialize};
+use serde_json::{Value, json};
+
+const PACKAGED_VECTORS: &str = include_str!("fixtures/deletion_suppression.v1.json");
+const WORKSPACE_VECTOR_PATH: &str =
+ "../../contracts/conformance/vectors/deletion/suppression.v1.json";
+const WORKSPACE_CONTRACT_MARKER_PATH: &str = "../../contracts/manifest.toml";
+const OPERATION: &str = "social.deletion_request.evaluate_suppression.valid";
+
+const VECTOR_EXPECTATIONS: [(&str, &str, &str); 32] = [
+ (
+ "nip09_suppress_no_requests_visible",
+ "visible",
+ "deletion_no_authorized_reference",
+ ),
+ (
+ "nip09_suppress_same_author_event_reference",
+ "suppressed",
+ "deletion_event_id_reference",
+ ),
+ (
+ "nip09_suppress_same_author_nonmatching_reference",
+ "visible",
+ "deletion_no_authorized_reference",
+ ),
+ (
+ "nip09_suppress_wrong_author_exact_event_reference",
+ "visible",
+ "deletion_request_author_mismatch",
+ ),
+ (
+ "nip09_suppress_event_reference_predates_target",
+ "suppressed",
+ "deletion_event_id_reference",
+ ),
+ (
+ "nip09_suppress_deletion_request_immune_event_reference",
+ "visible",
+ "deletion_request_immune",
+ ),
+ (
+ "nip09_suppress_deletion_request_immune_mixed_references",
+ "visible",
+ "deletion_request_immune",
+ ),
+ (
+ "nip09_suppress_address_cutoff_before_target",
+ "visible",
+ "deletion_address_cutoff_precedes_target",
+ ),
+ (
+ "nip09_suppress_address_cutoff_equal_target",
+ "suppressed",
+ "deletion_address_reference",
+ ),
+ (
+ "nip09_suppress_address_cutoff_after_target",
+ "suppressed",
+ "deletion_address_reference",
+ ),
+ (
+ "nip09_suppress_address_wrong_kind",
+ "visible",
+ "deletion_no_authorized_reference",
+ ),
+ (
+ "nip09_suppress_address_wrong_identifier",
+ "visible",
+ "deletion_no_authorized_reference",
+ ),
+ (
+ "nip09_suppress_address_wrong_pubkey",
+ "visible",
+ "deletion_no_authorized_reference",
+ ),
+ (
+ "nip09_suppress_wrong_author_address_reference",
+ "visible",
+ "deletion_request_author_mismatch",
+ ),
+ (
+ "nip09_suppress_replaceable_kind_0",
+ "suppressed",
+ "deletion_address_reference",
+ ),
+ (
+ "nip09_suppress_replaceable_kind_3",
+ "suppressed",
+ "deletion_address_reference",
+ ),
+ (
+ "nip09_suppress_replaceable_kind_10000",
+ "suppressed",
+ "deletion_address_reference",
+ ),
+ (
+ "nip09_suppress_replaceable_kind_19999",
+ "suppressed",
+ "deletion_address_reference",
+ ),
+ (
+ "nip09_suppress_addressable_kind_30000_empty_identifier",
+ "suppressed",
+ "deletion_address_reference",
+ ),
+ (
+ "nip09_suppress_addressable_kind_39999_opaque_identifier",
+ "suppressed",
+ "deletion_address_reference",
+ ),
+ (
+ "nip09_suppress_combined_event_and_address_references",
+ "suppressed",
+ "deletion_event_id_and_address_reference",
+ ),
+ (
+ "nip09_suppress_event_reference_with_stale_address",
+ "suppressed",
+ "deletion_event_id_reference",
+ ),
+ (
+ "nip09_suppress_kind_advisory_diagnostics_ignored",
+ "suppressed",
+ "deletion_event_id_reference",
+ ),
+ (
+ "nip09_suppress_duplicate_raw_targets_deduplicated",
+ "suppressed",
+ "deletion_event_id_and_address_reference",
+ ),
+ (
+ "nip09_suppress_max_address_cutoff_forward_order",
+ "suppressed",
+ "deletion_address_reference",
+ ),
+ (
+ "nip09_suppress_max_address_cutoff_reverse_order",
+ "suppressed",
+ "deletion_address_reference",
+ ),
+ (
+ "nip09_suppress_later_revision_survives_address_cutoff",
+ "visible",
+ "deletion_address_cutoff_precedes_target",
+ ),
+ (
+ "nip09_suppress_equal_max_cutoff_uses_lowest_request_id",
+ "suppressed",
+ "deletion_address_reference",
+ ),
+ (
+ "nip09_suppress_multiple_event_references_use_lowest_request_id",
+ "suppressed",
+ "deletion_event_id_reference",
+ ),
+ (
+ "nip09_suppress_repeated_request_is_idempotent",
+ "suppressed",
+ "deletion_event_id_reference",
+ ),
+ (
+ "nip09_suppress_unauthorized_event_plus_stale_authorized_address",
+ "visible",
+ "deletion_address_cutoff_precedes_target",
+ ),
+ (
+ "nip09_suppress_exact_event_deletes_later_replacement",
+ "suppressed",
+ "deletion_event_id_reference",
+ ),
+];
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct Suite {
+ suite: String,
+ contract_version: String,
+ vectors: Vec<Vector>,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct Vector {
+ id: String,
+ kind: String,
+ input: Input,
+ expected: ExpectedDecision,
+}
+
+#[derive(Debug, Deserialize)]
+#[serde(deny_unknown_fields)]
+struct Input {
+ request_event_jsons: Vec<String>,
+ target_event_json: String,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[serde(deny_unknown_fields)]
+struct ExpectedDecision {
+ outcome: String,
+ reason: String,
+ event_reference: Option<ExpectedEventReference>,
+ address_reference: Option<ExpectedAddressReference>,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[serde(deny_unknown_fields)]
+struct ExpectedEventReference {
+ request_id: String,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
+#[serde(deny_unknown_fields)]
+struct ExpectedAddressReference {
+ coordinate: String,
+ inclusive_cutoff: u64,
+ request_id: String,
+}
+
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(deny_unknown_fields)]
+struct RawEvent {
+ id: String,
+ pubkey: String,
+ created_at: u64,
+ kind: u32,
+ tags: Vec<Vec<String>>,
+ content: String,
+ sig: String,
+}
+
+#[test]
+fn suppression_vectors_execute_the_public_evaluator() {
+ let vectors = conformance_vectors();
+ let raw_suite: Value =
+ serde_json::from_str(&vectors).expect("NIP-09 suppression JSON must parse");
+ validate_no_forbidden_corpus_metadata(&raw_suite, "$")
+ .unwrap_or_else(|error| panic!("{error}"));
+ assert_exact_nullable_evidence_shape(&raw_suite);
+
+ let suite: Suite =
+ serde_json::from_str(&vectors).expect("NIP-09 suppression vectors must parse");
+ assert_eq!(suite.suite, "nip09_suppression_evaluator");
+ assert_eq!(suite.contract_version, "1.0.0");
+ assert_vector_inventory(&suite.vectors);
+ assert_signed_event_inventory(&suite.vectors);
+
+ for vector in &suite.vectors {
+ execute(vector);
+ }
+}
+
+fn assert_vector_inventory(vectors: &[Vector]) {
+ let expected = VECTOR_EXPECTATIONS
+ .into_iter()
+ .map(|(id, outcome, reason)| (id, (OPERATION, outcome, reason)))
+ .collect::<BTreeMap<_, _>>();
+ let actual = vectors
+ .iter()
+ .map(|vector| {
+ (
+ vector.id.as_str(),
+ (
+ vector.kind.as_str(),
+ vector.expected.outcome.as_str(),
+ vector.expected.reason.as_str(),
+ ),
+ )
+ })
+ .collect::<BTreeMap<_, _>>();
+ assert_eq!(vectors.len(), VECTOR_EXPECTATIONS.len());
+ assert_eq!(actual, expected);
+ assert!(vectors.iter().all(|vector| vector.id.starts_with("nip09_")));
+
+ let reasons = vectors
+ .iter()
+ .map(|vector| vector.expected.reason.as_str())
+ .collect::<BTreeSet<_>>();
+ assert_eq!(
+ reasons,
+ BTreeSet::from([
+ "deletion_address_cutoff_precedes_target",
+ "deletion_address_reference",
+ "deletion_event_id_and_address_reference",
+ "deletion_event_id_reference",
+ "deletion_no_authorized_reference",
+ "deletion_request_author_mismatch",
+ "deletion_request_immune",
+ ])
+ );
+}
+
+fn assert_signed_event_inventory(vectors: &[Vector]) {
+ let event_jsons = vectors
+ .iter()
+ .flat_map(|vector| {
+ std::iter::once(vector.input.target_event_json.as_str())
+ .chain(vector.input.request_event_jsons.iter().map(String::as_str))
+ })
+ .collect::<BTreeSet<_>>();
+ assert_eq!(event_jsons.len(), 38, "signed event inventory drifted");
+
+ let mut deletion_request_count = 0usize;
+ for event_json in event_jsons {
+ let verified = verified_event(event_json, "signed event inventory");
+ if verified.event().kind_u32() == 5 {
+ admit_verified_nip09_deletion_request_event(verified)
+ .expect("every fixed kind-5 corpus event must admit");
+ deletion_request_count += 1;
+ }
+ }
+ assert_eq!(deletion_request_count, 30);
+}
+
+fn execute(vector: &Vector) {
+ let target = verified_event(&vector.input.target_event_json, &vector.id);
+ let requests = vector
+ .input
+ .request_event_jsons
+ .iter()
+ .map(|event_json| admitted_request(event_json, &vector.id))
+ .collect::<Vec<_>>();
+ let target_before = target.clone();
+ let requests_before = requests.clone();
+
+ let decision = evaluate_nip09_suppression(&target, &requests);
+ assert_eq!(actual_decision(&decision), vector.expected, "{}", vector.id);
+ assert_eq!(target, target_before, "{} target changed", vector.id);
+ assert_eq!(requests, requests_before, "{} requests changed", vector.id);
+
+ let repeated = evaluate_nip09_suppression(&target, &requests);
+ assert_eq!(
+ repeated, decision,
+ "{} repeat evaluation drifted",
+ vector.id
+ );
+ let mut reversed = requests.clone();
+ reversed.reverse();
+ assert_eq!(
+ evaluate_nip09_suppression(&target, &reversed),
+ decision,
+ "{} request order changed the decision",
+ vector.id
+ );
+}
+
+fn actual_decision(decision: &RadrootsNip09SuppressionDecision) -> ExpectedDecision {
+ ExpectedDecision {
+ outcome: decision.outcome().code().to_owned(),
+ reason: decision.reason().code().to_owned(),
+ event_reference: decision
+ .event_reference()
+ .map(|evidence| ExpectedEventReference {
+ request_id: evidence.request_id().as_str().to_owned(),
+ }),
+ address_reference: decision
+ .address_reference()
+ .map(|evidence| ExpectedAddressReference {
+ coordinate: evidence.coordinate().as_str().to_owned(),
+ inclusive_cutoff: evidence.inclusive_cutoff(),
+ request_id: evidence.request_id().as_str().to_owned(),
+ }),
+ }
+}
+
+fn admitted_request(
+ event_json: &str,
+ vector_id: &str,
+) -> RadrootsAdmittedNip09DeletionRequestEvent {
+ let verified = verified_event(event_json, vector_id);
+ admit_verified_nip09_deletion_request_event(verified)
+ .unwrap_or_else(|error| panic!("{vector_id} request admission failed: {error}"))
+}
+
+fn verified_event(event_json: &str, vector_id: &str) -> RadrootsSignatureVerifiedEvent {
+ verify_nip01_event(event_envelope(event_json, vector_id))
+ .unwrap_or_else(|error| panic!("{vector_id} signature verification failed: {error}"))
+}
+
+fn event_envelope(event_json: &str, vector_id: &str) -> RadrootsEventEnvelope {
+ let raw: RawEvent = serde_json::from_str(event_json)
+ .unwrap_or_else(|error| panic!("{vector_id} signed event JSON is invalid: {error}"));
+ assert_eq!(
+ serde_json::to_string(&raw).expect("raw event serialization"),
+ event_json,
+ "{vector_id} signed event JSON must be canonical and compact"
+ );
+ RadrootsEventEnvelope::new(RadrootsEventEnvelopeParts {
+ id: raw.id,
+ author: raw.pubkey,
+ created_at: raw.created_at,
+ kind: raw.kind,
+ tags: raw.tags,
+ content: raw.content,
+ sig: raw.sig,
+ })
+ .unwrap_or_else(|error| panic!("{vector_id} event envelope failed: {error}"))
+}
+
+fn assert_exact_nullable_evidence_shape(suite: &Value) {
+ let suite_object = suite.as_object().expect("suite object");
+ assert_eq!(
+ suite_object
+ .keys()
+ .map(String::as_str)
+ .collect::<BTreeSet<_>>(),
+ BTreeSet::from(["contract_version", "suite", "vectors"])
+ );
+ for vector in suite["vectors"].as_array().expect("vectors array") {
+ assert_eq!(
+ vector
+ .as_object()
+ .expect("vector object")
+ .keys()
+ .map(String::as_str)
+ .collect::<BTreeSet<_>>(),
+ BTreeSet::from(["expected", "id", "input", "kind"])
+ );
+ assert_eq!(
+ vector["input"]
+ .as_object()
+ .expect("input object")
+ .keys()
+ .map(String::as_str)
+ .collect::<BTreeSet<_>>(),
+ BTreeSet::from(["request_event_jsons", "target_event_json"])
+ );
+ assert_eq!(
+ vector["expected"]
+ .as_object()
+ .expect("expected object")
+ .keys()
+ .map(String::as_str)
+ .collect::<BTreeSet<_>>(),
+ BTreeSet::from(["address_reference", "event_reference", "outcome", "reason",])
+ );
+ }
+}
+
+fn conformance_vectors() -> Cow<'static, str> {
+ let workspace_path = Path::new(env!("CARGO_MANIFEST_DIR")).join(WORKSPACE_VECTOR_PATH);
+ match fs::read_to_string(&workspace_path) {
+ Ok(canonical) => {
+ assert_eq!(
+ canonical,
+ PACKAGED_VECTORS,
+ "packaged suppression vectors must match {}",
+ workspace_path.display()
+ );
+ Cow::Owned(canonical)
+ }
+ Err(error)
+ if error.kind() == std::io::ErrorKind::NotFound
+ && !Path::new(env!("CARGO_MANIFEST_DIR"))
+ .join(WORKSPACE_CONTRACT_MARKER_PATH)
+ .is_file() =>
+ {
+ Cow::Borrowed(PACKAGED_VECTORS)
+ }
+ Err(error) => panic!("failed to read {}: {error}", workspace_path.display()),
+ }
+}
+
+fn validate_no_forbidden_corpus_metadata(value: &Value, path: &str) -> Result<(), String> {
+ match value {
+ Value::Object(object) => {
+ for (key, child) in object {
+ if is_forbidden_metadata_key(key) {
+ return Err(format!(
+ "frozen NIP-09 suppression corpus contains forbidden key {path}.{key}"
+ ));
+ }
+ validate_no_forbidden_corpus_metadata(child, &format!("{path}.{key}"))?;
+ }
+ }
+ Value::Array(values) => {
+ for (index, child) in values.iter().enumerate() {
+ validate_no_forbidden_corpus_metadata(child, &format!("{path}[{index}]"))?;
+ }
+ }
+ Value::String(string) => {
+ if string.to_ascii_lowercase().contains("nsec1") {
+ return Err(format!(
+ "frozen NIP-09 suppression corpus contains nsec material at {path}"
+ ));
+ }
+ if contains_approved_fixture_secret(string) {
+ return Err(format!(
+ "frozen NIP-09 suppression corpus contains a fixture secret at {path}"
+ ));
+ }
+ }
+ _ => {}
+ }
+ Ok(())
+}
+
+fn is_forbidden_metadata_key(key: &str) -> bool {
+ let normalized = key.to_ascii_lowercase();
+ matches!(normalized.as_str(), "base" | "mutation")
+ || normalized.contains("source")
+ || normalized.contains("seed")
+ || normalized.contains("generator")
+ || normalized.contains("recipe")
+ || normalized.contains("secret_key")
+ || normalized.contains("private_key")
+ || normalized.contains("signing_key")
+ || normalized.contains("authorization")
+ || normalized.contains("authorized")
+ || normalized.contains("store_mutation")
+ || normalized == "effect"
+ || normalized.ends_with("_effect")
+ || normalized == "effects"
+}
+
+fn contains_approved_fixture_secret(value: &str) -> bool {
+ let normalized = value.to_ascii_lowercase();
+ radroots_test_fixtures::approved_fixture_identities()
+ .iter()
+ .any(|identity| normalized.contains(identity.secret_key_hex))
+}
+
+#[test]
+fn suppression_corpus_hygiene_rejects_generation_and_effect_authority_metadata() {
+ for key in [
+ "SOURCE",
+ "SeEd",
+ "GENERATOR",
+ "MuTaTiOn",
+ "AuThOrIzAtIoN",
+ "store_effect",
+ ] {
+ let value = Value::Object([(key.to_owned(), Value::Null)].into_iter().collect());
+ let error = validate_no_forbidden_corpus_metadata(&value, "$")
+ .expect_err("forbidden corpus metadata must fail");
+ assert!(error.contains(key), "{error}");
+ }
+ assert!(validate_no_forbidden_corpus_metadata(&json!("NSEC1FORBIDDEN"), "$").is_err());
+ for identity in radroots_test_fixtures::approved_fixture_identities() {
+ assert!(
+ validate_no_forbidden_corpus_metadata(&json!(identity.secret_key_hex), "$").is_err()
+ );
+ }
+}
+
+#[test]
+fn suppression_corpus_shapes_reject_unknown_fields() {
+ let input = json!({
+ "request_event_jsons": [],
+ "target_event_json": "{}",
+ "unexpected": null
+ });
+ serde_json::from_value::<Input>(input).expect_err("unknown input fields must fail");
+
+ let expected = json!({
+ "outcome": "visible",
+ "reason": "deletion_no_authorized_reference",
+ "event_reference": null,
+ "address_reference": null,
+ "unexpected": null
+ });
+ serde_json::from_value::<ExpectedDecision>(expected)
+ .expect_err("unknown expected fields must fail");
+
+ let evidence = json!({"request_id": "a", "unexpected": null});
+ serde_json::from_value::<ExpectedEventReference>(evidence)
+ .expect_err("unknown evidence fields must fail");
+}
diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs
@@ -12,6 +12,7 @@ use deletion_authority::{
DELETION_ADMIT_INVALID_IDS, DELETION_ADMIT_VALID_IDS, DELETION_AUTHORED_INVALID_IDS,
DELETION_AUTHORED_VALID_IDS, DELETION_CASE_KINDS, DELETION_CONFORMANCE_VECTOR_RELATIVE,
DELETION_OPERATION_EXPECTATIONS, DELETION_PROJECT_INVALID_IDS, DELETION_PROJECT_VALID_IDS,
+ DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE, DELETION_SUPPRESSION_VALID_IDS,
REQUIRED_DELETION_PUBLIC_TYPES,
};
use semver::Version;
@@ -46,7 +47,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); 17] = [
+const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 18] = [
(
"contracts/conformance/vectors/blossom/bud11_claims.v1.json",
"crates/blossom/tests/fixtures/bud11_claims.v1.json",
@@ -76,6 +77,10 @@ const CONFORMANCE_VECTOR_MIRRORS: [(&str, &str); 17] = [
"crates/event_codec/tests/fixtures/deletion_verified_profile.v1.json",
),
(
+ "contracts/conformance/vectors/deletion/suppression.v1.json",
+ "crates/event_codec/tests/fixtures/deletion_suppression.v1.json",
+ ),
+ (
"contracts/conformance/vectors/events/operational_listing_tags_full.v1.json",
"crates/event_codec/tests/fixtures/operational_listing_tags_full.v1.json",
),
@@ -1482,6 +1487,7 @@ struct CommentOperationExpectation {
struct DeletionOperationExpectation {
key: &'static str,
id: &'static str,
+ vector: &'static str,
inputs: &'static [&'static str],
outputs: &'static [&'static str],
error_class: &'static str,
@@ -1860,7 +1866,7 @@ const COMMENT_WITNESSES: [EventBoundarySourceWitness; 8] = [
},
];
-const DELETION_WITNESSES: [EventBoundarySourceWitness; 7] = [
+const DELETION_WITNESSES: [EventBoundarySourceWitness; 8] = [
EventBoundarySourceWitness {
relative_path: "crates/event/src/deletion.rs",
required_fragments: &["pub struct RadrootsAuthoredNip09DeletionRequest"],
@@ -1884,6 +1890,17 @@ const DELETION_WITNESSES: [EventBoundarySourceWitness; 7] = [
],
},
EventBoundarySourceWitness {
+ relative_path: "crates/event_codec/src/deletion/evaluator.rs",
+ required_fragments: &[
+ "pub enum RadrootsNip09SuppressionOutcome",
+ "pub enum RadrootsNip09SuppressionReason",
+ "pub struct RadrootsNip09EventReferenceEvidence",
+ "pub struct RadrootsNip09AddressReferenceEvidence",
+ "pub struct RadrootsNip09SuppressionDecision",
+ "pub fn evaluate_nip09_suppression",
+ ],
+ },
+ EventBoundarySourceWitness {
relative_path: "crates/nostr/src/events/deletion.rs",
required_fragments: &[
"pub struct RadrootsNostrNip09DeletionRequestEventBuilder",
@@ -2571,11 +2588,12 @@ const CANONICAL_EVENT_BOUNDARY_EXPECTATIONS: [EventBoundaryExpectation; 44] = [
EventBoundaryExpectation {
domain: "deletion_request",
kind: "5",
- radroots_type: "RadrootsAuthoredNip09DeletionRequest / RadrootsInboundNip09DeletionProjection / RadrootsAdmittedNip09DeletionRequestEvent / RadrootsNostrNip09DeletionRequestEventBuilder",
+ radroots_type: "RadrootsAuthoredNip09DeletionRequest / RadrootsInboundNip09DeletionProjection / RadrootsAdmittedNip09DeletionRequestEvent / RadrootsNip09SuppressionDecision / RadrootsNostrNip09DeletionRequestEventBuilder",
rpc_methods: &[
"social.deletion_request.build_authored_draft",
"social.deletion_request.project_verified_event",
"social.deletion_request.verify_and_admit_event",
+ "social.deletion_request.evaluate_suppression",
],
witnesses: &DELETION_WITNESSES,
},
@@ -4116,20 +4134,28 @@ fn validate_all_conformance_vectors(
}
let canonical_comment_path = workspace_root.join(COMMENT_CONFORMANCE_VECTOR_RELATIVE);
let canonical_deletion_path = workspace_root.join(DELETION_CONFORMANCE_VECTOR_RELATIVE);
+ let canonical_deletion_suppression_path =
+ workspace_root.join(DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE);
for path in paths {
let vector = validate_conformance_vector_file(&path, contract_version)?;
validate_comment_vector_namespace(&path, &canonical_comment_path, &vector)?;
- validate_deletion_vector_namespace(&path, &canonical_deletion_path, &vector)?;
+ validate_deletion_vector_namespace(
+ &path,
+ &canonical_deletion_path,
+ &canonical_deletion_suppression_path,
+ &vector,
+ )?;
}
Ok(())
}
fn validate_deletion_vector_namespace(
path: &Path,
- canonical_path: &Path,
+ canonical_request_path: &Path,
+ canonical_suppression_path: &Path,
vector: &ConformanceVectorFile,
) -> Result<(), String> {
- if path == canonical_path {
+ if path == canonical_request_path || path == canonical_suppression_path {
return Ok(());
}
if let Some(entry) = vector
@@ -4138,10 +4164,11 @@ fn validate_deletion_vector_namespace(
.find(|entry| entry.kind.starts_with("social.deletion_request."))
{
return Err(format!(
- "deletion conformance case kind {} in {} is outside canonical vector {}",
+ "deletion conformance case kind {} in {} is outside canonical vectors {} and {}",
entry.kind,
path.display(),
- canonical_path.display()
+ canonical_request_path.display(),
+ canonical_suppression_path.display()
));
}
Ok(())
@@ -5870,16 +5897,21 @@ fn validate_deletion_operation_authority(
manifest: &OperationsContractManifest,
workspace_root: &Path,
) -> Result<(), String> {
- let vector = validate_conformance_vector_file(
+ let request_vector = validate_conformance_vector_file(
&workspace_root.join(DELETION_CONFORMANCE_VECTOR_RELATIVE),
&manifest.contract.version,
)?;
- validate_deletion_operation_inventory(manifest, &vector)
+ let suppression_vector = validate_conformance_vector_file(
+ &workspace_root.join(DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE),
+ &manifest.contract.version,
+ )?;
+ validate_deletion_operation_inventory(manifest, &request_vector, &suppression_vector)
}
fn validate_deletion_operation_inventory(
manifest: &OperationsContractManifest,
- vector: &ConformanceVectorFile,
+ request_vector: &ConformanceVectorFile,
+ suppression_vector: &ConformanceVectorFile,
) -> Result<(), String> {
let shared_types = collect_non_empty_set(
&manifest.shared_types.public,
@@ -5893,6 +5925,7 @@ fn validate_deletion_operation_inventory(
.map(String::as_str)
.filter(|name| {
name.contains("Nip09Deletion")
+ || name.starts_with("RadrootsNip09")
|| name.starts_with("RadrootsNip01Coordinate")
|| matches!(
*name,
@@ -5918,6 +5951,7 @@ fn validate_deletion_operation_inventory(
.iter()
.filter(|(key, operation)| {
operation.conformance.vector == DELETION_CONFORMANCE_VECTOR_RELATIVE
+ || operation.conformance.vector == DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE
|| key.starts_with("social_deletion_request_")
|| operation.id.starts_with("social.deletion_request.")
})
@@ -5941,6 +5975,15 @@ fn validate_deletion_operation_inventory(
let mut owners = BTreeMap::new();
for expected in DELETION_OPERATION_EXPECTATIONS {
+ let vector = match expected.vector {
+ DELETION_CONFORMANCE_VECTOR_RELATIVE => request_vector,
+ DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE => suppression_vector,
+ unexpected => {
+ return Err(format!(
+ "deletion operation authority contains unsupported vector {unexpected}"
+ ));
+ }
+ };
let operation = manifest
.operations
.get(expected.key)
@@ -6005,7 +6048,7 @@ fn validate_deletion_operation_inventory(
expected.key,
"conformance.vector",
&operation.conformance.vector,
- DELETION_CONFORMANCE_VECTOR_RELATIVE,
+ expected.vector,
)?;
validate_operation_case_kinds(operation, vector)?;
if !operation
@@ -6040,7 +6083,7 @@ fn validate_deletion_operation_inventory(
}
let mut actual_inventory = BTreeMap::new();
- for entry in &vector.vectors {
+ for entry in &request_vector.vectors {
validate_deletion_vector_shape(entry)?;
if actual_inventory
.insert(entry.id.as_str(), entry.kind.as_str())
@@ -6106,6 +6149,409 @@ fn validate_deletion_operation_inventory(
));
}
+ validate_deletion_suppression_vector_inventory(suppression_vector, &owners)
+}
+
+fn validate_deletion_suppression_vector_inventory(
+ vector: &ConformanceVectorFile,
+ owners: &BTreeMap<&str, &str>,
+) -> Result<(), String> {
+ if vector.suite != "nip09_suppression_evaluator" {
+ return Err(format!(
+ "deletion suppression conformance suite drift: expected nip09_suppression_evaluator, got {}",
+ vector.suite
+ ));
+ }
+
+ let expected_kind = "social.deletion_request.evaluate_suppression.valid";
+ if owners.get(expected_kind).copied() != Some("social_deletion_request_evaluate_suppression") {
+ return Err(format!(
+ "deletion suppression conformance kind {expected_kind} is not owned by the evaluator operation"
+ ));
+ }
+
+ let expected_inventory = DELETION_SUPPRESSION_VALID_IDS
+ .into_iter()
+ .map(|id| (id, expected_kind))
+ .collect::<BTreeMap<_, _>>();
+ let mut actual_inventory = BTreeMap::new();
+ for entry in &vector.vectors {
+ validate_deletion_suppression_vector_shape(entry)?;
+ if actual_inventory
+ .insert(entry.id.as_str(), entry.kind.as_str())
+ .is_some()
+ {
+ return Err(format!(
+ "deletion suppression conformance vector inventory has duplicate id {}",
+ entry.id
+ ));
+ }
+ if !entry.id.starts_with("nip09_suppress_") {
+ return Err(format!(
+ "deletion suppression conformance vector id {} must use the nip09_suppress_ prefix",
+ entry.id
+ ));
+ }
+ if entry.kind != expected_kind {
+ return Err(format!(
+ "deletion suppression conformance vector {} kind drift: expected {expected_kind}, got {}",
+ entry.id, entry.kind
+ ));
+ }
+ }
+ if actual_inventory != expected_inventory {
+ return Err(format!(
+ "deletion suppression conformance vector inventory drift: expected {:?}, got {:?}",
+ expected_inventory, actual_inventory
+ ));
+ }
+
+ Ok(())
+}
+
+fn validate_deletion_suppression_vector_shape(
+ entry: &ConformanceVectorEntry,
+) -> Result<(), String> {
+ validate_deletion_suppression_forbidden_material(&entry.input, &format!("{}.input", entry.id))?;
+ validate_deletion_suppression_forbidden_material(
+ &entry.expected,
+ &format!("{}.expected", entry.id),
+ )?;
+
+ let input = deletion_object(&entry.input, &format!("{} input", entry.id))?;
+ validate_deletion_object_keys(
+ input,
+ &format!("{} input", entry.id),
+ &["request_event_jsons", "target_event_json"],
+ )?;
+ let target_event_json = input
+ .get("target_event_json")
+ .and_then(Value::as_str)
+ .ok_or_else(|| {
+ format!(
+ "deletion suppression vector {} input.target_event_json must be a string",
+ entry.id
+ )
+ })?;
+ validate_deletion_suppression_fixed_event(
+ target_event_json,
+ &format!("{} target_event_json", entry.id),
+ None,
+ )?;
+
+ let request_event_jsons = input
+ .get("request_event_jsons")
+ .and_then(Value::as_array)
+ .ok_or_else(|| {
+ format!(
+ "deletion suppression vector {} input.request_event_jsons must be an array",
+ entry.id
+ )
+ })?;
+ let mut request_ids = BTreeSet::new();
+ for (index, value) in request_event_jsons.iter().enumerate() {
+ let event_json = value.as_str().ok_or_else(|| {
+ format!(
+ "deletion suppression vector {} input.request_event_jsons[{index}] must be a string",
+ entry.id
+ )
+ })?;
+ let event = validate_deletion_suppression_fixed_event(
+ event_json,
+ &format!("{} request_event_jsons[{index}]", entry.id),
+ Some(5),
+ )?;
+ request_ids.insert(event.id);
+ }
+
+ let expected = deletion_object(&entry.expected, &format!("{} expected", entry.id))?;
+ validate_deletion_object_keys(
+ expected,
+ &format!("{} expected", entry.id),
+ &["address_reference", "event_reference", "outcome", "reason"],
+ )?;
+ let outcome = expected
+ .get("outcome")
+ .and_then(Value::as_str)
+ .ok_or_else(|| {
+ format!(
+ "deletion suppression vector {} expected.outcome must be a string",
+ entry.id
+ )
+ })?;
+ if !matches!(outcome, "visible" | "suppressed") {
+ return Err(format!(
+ "deletion suppression vector {} expected.outcome is unsupported: {outcome}",
+ entry.id
+ ));
+ }
+ let reason = expected
+ .get("reason")
+ .and_then(Value::as_str)
+ .ok_or_else(|| {
+ format!(
+ "deletion suppression vector {} expected.reason must be a string",
+ entry.id
+ )
+ })?;
+ if !matches!(
+ reason,
+ "deletion_request_immune"
+ | "deletion_no_authorized_reference"
+ | "deletion_request_author_mismatch"
+ | "deletion_address_cutoff_precedes_target"
+ | "deletion_event_id_reference"
+ | "deletion_address_reference"
+ | "deletion_event_id_and_address_reference"
+ ) {
+ return Err(format!(
+ "deletion suppression vector {} expected.reason is unsupported: {reason}",
+ entry.id
+ ));
+ }
+
+ let event_reference = validate_deletion_suppression_event_reference(
+ expected
+ .get("event_reference")
+ .expect("exact expected keys contain event_reference"),
+ &entry.id,
+ &request_ids,
+ )?;
+ let address_reference = validate_deletion_suppression_address_reference(
+ expected
+ .get("address_reference")
+ .expect("exact expected keys contain address_reference"),
+ &entry.id,
+ &request_ids,
+ )?;
+ let decision_shape_matches = match reason {
+ "deletion_request_immune"
+ | "deletion_no_authorized_reference"
+ | "deletion_request_author_mismatch" => {
+ outcome == "visible" && !event_reference && !address_reference
+ }
+ "deletion_address_cutoff_precedes_target" => {
+ outcome == "visible" && !event_reference && address_reference
+ }
+ "deletion_address_reference" => {
+ outcome == "suppressed" && !event_reference && address_reference
+ }
+ "deletion_event_id_and_address_reference" => {
+ outcome == "suppressed" && event_reference && address_reference
+ }
+ "deletion_event_id_reference" => outcome == "suppressed" && event_reference,
+ _ => unreachable!("supported reason matched above"),
+ };
+ if !decision_shape_matches {
+ return Err(format!(
+ "deletion suppression vector {} expected decision shape is inconsistent with reason {reason}",
+ entry.id
+ ));
+ }
+
+ Ok(())
+}
+
+fn validate_deletion_suppression_fixed_event(
+ event_json: &str,
+ label: &str,
+ expected_kind: Option<u32>,
+) -> Result<DeletionConformanceRawEvent, String> {
+ if contains_nsec_material(event_json) {
+ return Err(format!(
+ "deletion suppression vector {label} contains forbidden nsec material"
+ ));
+ }
+ if contains_approved_fixture_secret(event_json) {
+ return Err(format!(
+ "deletion suppression vector {label} contains forbidden approved fixture secret material"
+ ));
+ }
+ let raw = serde_json::from_str::<DeletionConformanceRawEvent>(event_json).map_err(|error| {
+ format!("deletion suppression vector {label} has invalid fixed event shape: {error}")
+ })?;
+ if expected_kind.is_some_and(|kind| raw.kind != kind) {
+ return Err(format!(
+ "deletion suppression vector {label} must contain a kind-5 deletion request"
+ ));
+ }
+ let canonical = serde_json::to_string(&raw).map_err(|error| {
+ format!("deletion suppression vector {label} cannot be reserialized: {error}")
+ })?;
+ if canonical != event_json {
+ return Err(format!(
+ "deletion suppression vector {label} must be compact canonical JSON"
+ ));
+ }
+ Ok(raw)
+}
+
+fn validate_deletion_suppression_event_reference(
+ value: &Value,
+ id: &str,
+ request_ids: &BTreeSet<String>,
+) -> Result<bool, String> {
+ if value.is_null() {
+ return Ok(false);
+ }
+ let reference = deletion_object(value, &format!("{id} expected.event_reference"))?;
+ validate_deletion_object_keys(
+ reference,
+ &format!("{id} expected.event_reference"),
+ &["request_id"],
+ )?;
+ validate_deletion_suppression_request_id(
+ reference.get("request_id"),
+ id,
+ "event_reference.request_id",
+ request_ids,
+ )?;
+ Ok(true)
+}
+
+fn validate_deletion_suppression_address_reference(
+ value: &Value,
+ id: &str,
+ request_ids: &BTreeSet<String>,
+) -> Result<bool, String> {
+ if value.is_null() {
+ return Ok(false);
+ }
+ let reference = deletion_object(value, &format!("{id} expected.address_reference"))?;
+ validate_deletion_object_keys(
+ reference,
+ &format!("{id} expected.address_reference"),
+ &["coordinate", "inclusive_cutoff", "request_id"],
+ )?;
+ let coordinate = reference
+ .get("coordinate")
+ .and_then(Value::as_str)
+ .ok_or_else(|| {
+ format!(
+ "deletion suppression vector {id} expected.address_reference.coordinate must be a string"
+ )
+ })?;
+ validate_deletion_suppression_coordinate(coordinate, id)?;
+ if !reference
+ .get("inclusive_cutoff")
+ .is_some_and(|value| value.as_u64().is_some())
+ {
+ return Err(format!(
+ "deletion suppression vector {id} expected.address_reference.inclusive_cutoff must be an unsigned integer"
+ ));
+ }
+ validate_deletion_suppression_request_id(
+ reference.get("request_id"),
+ id,
+ "address_reference.request_id",
+ request_ids,
+ )?;
+ Ok(true)
+}
+
+fn validate_deletion_suppression_request_id(
+ value: Option<&Value>,
+ id: &str,
+ field: &str,
+ request_ids: &BTreeSet<String>,
+) -> Result<(), String> {
+ let request_id = value.and_then(Value::as_str).ok_or_else(|| {
+ format!("deletion suppression vector {id} expected.{field} must be a string")
+ })?;
+ if request_id.len() != 64
+ || !request_id
+ .bytes()
+ .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
+ {
+ return Err(format!(
+ "deletion suppression vector {id} expected.{field} must be lowercase 64-character hex"
+ ));
+ }
+ if !request_ids.contains(request_id) {
+ return Err(format!(
+ "deletion suppression vector {id} expected.{field} must identify an input request"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_deletion_suppression_coordinate(coordinate: &str, id: &str) -> Result<(), String> {
+ let mut parts = coordinate.splitn(3, ':');
+ let kind_text = parts.next().unwrap_or_default();
+ let pubkey = parts.next().unwrap_or_default();
+ let identifier = parts.next().ok_or_else(|| {
+ format!(
+ "deletion suppression vector {id} expected.address_reference.coordinate has invalid format"
+ )
+ })?;
+ let kind = kind_text.parse::<u32>().map_err(|_| {
+ format!(
+ "deletion suppression vector {id} expected.address_reference.coordinate kind is invalid"
+ )
+ })?;
+ if kind.to_string() != kind_text
+ || pubkey.len() != 64
+ || !pubkey
+ .bytes()
+ .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
+ || (!matches!(kind, 0 | 3)
+ && !(10_000..=19_999).contains(&kind)
+ && !(30_000..=39_999).contains(&kind))
+ || ((matches!(kind, 0 | 3) || (10_000..=19_999).contains(&kind)) && !identifier.is_empty())
+ {
+ return Err(format!(
+ "deletion suppression vector {id} expected.address_reference.coordinate is not canonical"
+ ));
+ }
+ Ok(())
+}
+
+fn validate_deletion_suppression_forbidden_material(
+ value: &Value,
+ path: &str,
+) -> Result<(), String> {
+ match value {
+ Value::Object(object) => {
+ for (key, child) in object {
+ let normalized = key.to_ascii_lowercase();
+ if matches!(normalized.as_str(), "base" | "mutation")
+ || normalized.contains("seed")
+ || normalized.contains("generator")
+ || normalized.contains("recipe")
+ || normalized.contains("secret_key")
+ || normalized.contains("private_key")
+ || normalized.contains("signing_key")
+ || normalized.contains("boundary")
+ {
+ return Err(format!(
+ "deletion suppression vector contains forbidden metadata key {path}.{key}"
+ ));
+ }
+ validate_deletion_suppression_forbidden_material(child, &format!("{path}.{key}"))?;
+ }
+ }
+ Value::Array(values) => {
+ for (index, child) in values.iter().enumerate() {
+ validate_deletion_suppression_forbidden_material(
+ child,
+ &format!("{path}[{index}]"),
+ )?;
+ }
+ }
+ Value::String(string) => {
+ if contains_nsec_material(string) {
+ return Err(format!(
+ "deletion suppression vector contains forbidden nsec material at {path}"
+ ));
+ }
+ if contains_approved_fixture_secret(string) {
+ return Err(format!(
+ "deletion suppression vector contains forbidden approved fixture secret material at {path}"
+ ));
+ }
+ }
+ _ => {}
+ }
Ok(())
}
@@ -7985,15 +8431,23 @@ mod tests {
(manifest, vector)
}
- fn current_deletion_authority() -> (OperationsContractManifest, ConformanceVectorFile) {
+ fn current_deletion_authority() -> (
+ OperationsContractManifest,
+ ConformanceVectorFile,
+ ConformanceVectorFile,
+ ) {
let root = workspace_root();
let manifest =
parse_toml::<OperationsContractManifest>(&root.join("contracts/operations.toml"))
.expect("current operations manifest");
- let vector =
+ let request_vector =
parse_json::<ConformanceVectorFile>(&root.join(DELETION_CONFORMANCE_VECTOR_RELATIVE))
.expect("current deletion conformance vector");
- (manifest, vector)
+ let suppression_vector = parse_json::<ConformanceVectorFile>(
+ &root.join(DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE),
+ )
+ .expect("current deletion suppression conformance vector");
+ (manifest, request_vector, suppression_vector)
}
fn current_food_availability_authority() -> (OperationsContractManifest, ConformanceVectorFile)
@@ -9030,34 +9484,36 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"]
#[test]
fn deletion_operation_authority_rejects_contract_drift() {
- let (manifest, vector) = current_deletion_authority();
- validate_deletion_operation_inventory(&manifest, &vector)
+ let (manifest, request_vector, suppression_vector) = current_deletion_authority();
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
.expect("current deletion operation authority");
- let (mut manifest, vector) = current_deletion_authority();
+ let (mut manifest, request_vector, suppression_vector) = current_deletion_authority();
manifest
.operations
.remove("social_deletion_request_project_verified_event");
- let error = validate_deletion_operation_inventory(&manifest, &vector)
- .expect_err("missing deletion operation must fail");
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("missing deletion operation must fail");
assert!(
error.contains("deletion operation authority drift"),
"{error}"
);
- let (mut manifest, vector) = current_deletion_authority();
+ let (mut manifest, request_vector, suppression_vector) = current_deletion_authority();
manifest
.shared_types
.public
.push("RadrootsNip09DeletionUnauthorizedEffect".to_string());
- let error = validate_deletion_operation_inventory(&manifest, &vector)
- .expect_err("unexpected deletion public type must fail");
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("unexpected deletion public type must fail");
assert!(
error.contains("deletion operation public-type authority drift"),
"{error}"
);
- let (mut manifest, vector) = current_deletion_authority();
+ let (mut manifest, request_vector, suppression_vector) = current_deletion_authority();
manifest
.operations
.get_mut("social_deletion_request_verify_and_admit_event")
@@ -9065,53 +9521,83 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"]
.conformance
.case_kinds
.pop();
- let error = validate_deletion_operation_inventory(&manifest, &vector)
- .expect_err("missing deletion case kind must fail");
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("missing deletion case kind must fail");
assert!(error.contains("conformance.case_kinds drift"), "{error}");
+
+ let (mut manifest, request_vector, suppression_vector) = current_deletion_authority();
+ manifest
+ .operations
+ .get_mut("social_deletion_request_evaluate_suppression")
+ .expect("suppression operation")
+ .conformance
+ .vector = DELETION_CONFORMANCE_VECTOR_RELATIVE.to_string();
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("suppression vector ownership drift must fail");
+ assert!(error.contains("conformance.vector drift"), "{error}");
}
#[test]
fn deletion_operation_authority_rejects_vector_inventory_drift() {
- let (manifest, mut vector) = current_deletion_authority();
- vector
+ let (manifest, mut request_vector, suppression_vector) = current_deletion_authority();
+ request_vector
.vectors
.iter_mut()
.find(|entry| entry.id == "nip09_authored_event_target_min_kind_empty_content")
.expect("authored deletion vector")
.id = "nip09_authored_event_target_min_kind_replacement".to_string();
- let error = validate_deletion_operation_inventory(&manifest, &vector)
- .expect_err("same-count deletion vector ID replacement must fail");
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("same-count deletion vector ID replacement must fail");
assert!(
error.contains("deletion conformance vector inventory drift"),
"{error}"
);
- let (manifest, mut vector) = current_deletion_authority();
- let source = vector
+ let (manifest, mut request_vector, suppression_vector) = current_deletion_authority();
+ let source = request_vector
.vectors
.iter()
.find(|entry| entry.id == "nip09_project_signed_event_target_without_k")
.expect("valid signed deletion vector");
let input = source.input.clone();
let expected = source.expected.clone();
- vector.vectors.push(ConformanceVectorEntry {
+ request_vector.vectors.push(ConformanceVectorEntry {
id: "nip09_unclaimed_case".to_string(),
kind: "social.deletion_request.unclaimed.valid".to_string(),
input,
expected,
});
- let error = validate_deletion_operation_inventory(&manifest, &vector)
- .expect_err("unclaimed deletion vector kind must fail");
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("unclaimed deletion vector kind must fail");
assert!(
error.contains("is not claimed by exactly one operation"),
"{error}"
);
+
+ let (manifest, request_vector, mut suppression_vector) = current_deletion_authority();
+ suppression_vector
+ .vectors
+ .iter_mut()
+ .find(|entry| entry.id == "nip09_suppress_no_requests_visible")
+ .expect("visible suppression vector")
+ .id = "nip09_suppress_replacement_visible".to_string();
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("same-count suppression vector ID replacement must fail");
+ assert!(
+ error.contains("deletion suppression conformance vector inventory drift"),
+ "{error}"
+ );
}
#[test]
fn deletion_operation_authority_rejects_generation_and_effect_metadata() {
- let (manifest, mut vector) = current_deletion_authority();
- vector
+ let (manifest, mut request_vector, suppression_vector) = current_deletion_authority();
+ request_vector
.vectors
.iter_mut()
.find(|entry| entry.id == "nip09_authored_event_target_min_kind_empty_content")
@@ -9120,12 +9606,13 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"]
.as_object_mut()
.expect("authored input")
.insert("SeEd".to_string(), Value::from(7_u64));
- let error = validate_deletion_operation_inventory(&manifest, &vector)
- .expect_err("generation seed metadata must fail");
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("generation seed metadata must fail");
assert!(error.contains("forbidden metadata key"), "{error}");
- let (manifest, mut vector) = current_deletion_authority();
- vector
+ let (manifest, mut request_vector, suppression_vector) = current_deletion_authority();
+ request_vector
.vectors
.iter_mut()
.find(|entry| entry.id == "nip09_project_signed_event_target_without_k")
@@ -9134,12 +9621,13 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"]
.as_object_mut()
.expect("signed input")
.insert("trace".to_string(), Value::Bool(true));
- let error = validate_deletion_operation_inventory(&manifest, &vector)
- .expect_err("extra signed input key must fail");
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("extra signed input key must fail");
assert!(error.contains("input keys drift"), "{error}");
- let (manifest, mut vector) = current_deletion_authority();
- vector
+ let (manifest, mut request_vector, suppression_vector) = current_deletion_authority();
+ request_vector
.vectors
.iter_mut()
.find(|entry| entry.id == "nip09_project_signed_event_target_without_k")
@@ -9148,12 +9636,13 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"]
.as_object_mut()
.expect("projection expected")
.insert("AuThOrIzAtIoN".to_string(), Value::Bool(true));
- let error = validate_deletion_operation_inventory(&manifest, &vector)
- .expect_err("effect-authority output must fail");
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("effect-authority output must fail");
assert!(error.contains("forbidden metadata key"), "{error}");
- let (manifest, mut vector) = current_deletion_authority();
- vector
+ let (manifest, mut request_vector, suppression_vector) = current_deletion_authority();
+ request_vector
.vectors
.iter_mut()
.find(|entry| entry.id == "nip09_admit_signed_event_target")
@@ -9165,12 +9654,13 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"]
"event_json".to_string(),
Value::String(r#"{"content":"NSEC1FORBIDDEN"}"#.to_string()),
);
- let error = validate_deletion_operation_inventory(&manifest, &vector)
- .expect_err("nsec material must fail");
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("nsec material must fail");
assert!(error.contains("forbidden nsec material"), "{error}");
- let (manifest, mut vector) = current_deletion_authority();
- vector
+ let (manifest, mut request_vector, suppression_vector) = current_deletion_authority();
+ request_vector
.vectors
.iter_mut()
.find(|entry| entry.id == "nip09_admit_signed_event_target")
@@ -9185,8 +9675,9 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"]
.to_string(),
),
);
- let error = validate_deletion_operation_inventory(&manifest, &vector)
- .expect_err("approved fixture secret material must fail");
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("approved fixture secret material must fail");
assert!(
error.contains("forbidden approved fixture secret material"),
"{error}"
@@ -9194,8 +9685,56 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"]
}
#[test]
+ fn deletion_suppression_authority_rejects_shape_and_material_drift() {
+ let (manifest, request_vector, mut suppression_vector) = current_deletion_authority();
+ suppression_vector.suite = "nip09_suppression_shadow".to_string();
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("suppression suite drift must fail");
+ assert!(
+ error.contains("deletion suppression conformance suite drift"),
+ "{error}"
+ );
+
+ let (manifest, request_vector, mut suppression_vector) = current_deletion_authority();
+ suppression_vector
+ .vectors
+ .iter_mut()
+ .find(|entry| entry.id == "nip09_suppress_no_requests_visible")
+ .expect("visible suppression vector")
+ .input
+ .as_object_mut()
+ .expect("suppression input")
+ .insert("seed".to_string(), Value::from(7_u64));
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("suppression generation metadata must fail");
+ assert!(error.contains("forbidden metadata key"), "{error}");
+
+ let (manifest, request_vector, mut suppression_vector) = current_deletion_authority();
+ suppression_vector
+ .vectors
+ .iter_mut()
+ .find(|entry| entry.id == "nip09_suppress_same_author_event_reference")
+ .expect("event-reference suppression vector")
+ .expected
+ .as_object_mut()
+ .expect("suppression expected")
+ .get_mut("event_reference")
+ .expect("event-reference evidence")
+ .as_object_mut()
+ .expect("event-reference object")
+ .insert("request_id".to_string(), Value::String("0".repeat(64)));
+ let error =
+ validate_deletion_operation_inventory(&manifest, &request_vector, &suppression_vector)
+ .expect_err("evidence not bound to an input request must fail");
+ assert!(error.contains("must identify an input request"), "{error}");
+ }
+
+ #[test]
fn deletion_vector_namespace_rejects_alternate_owners() {
- let canonical = PathBuf::from(DELETION_CONFORMANCE_VECTOR_RELATIVE);
+ let canonical_request = PathBuf::from(DELETION_CONFORMANCE_VECTOR_RELATIVE);
+ let canonical_suppression = PathBuf::from(DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE);
let alternate = PathBuf::from("contracts/conformance/vectors/social/mvp.v1.json");
let vector = ConformanceVectorFile {
suite: "alternate".to_string(),
@@ -9208,11 +9747,28 @@ crates = ["radroots_a", "radroots_b", "radroots_c", "radroots_d", "radroots_e"]
}],
};
- let error = validate_deletion_vector_namespace(&alternate, &canonical, &vector)
- .expect_err("alternate deletion vector namespace must fail");
- assert!(error.contains("outside canonical vector"), "{error}");
- validate_deletion_vector_namespace(&canonical, &canonical, &vector)
- .expect("canonical deletion vector owns the namespace");
+ let error = validate_deletion_vector_namespace(
+ &alternate,
+ &canonical_request,
+ &canonical_suppression,
+ &vector,
+ )
+ .expect_err("alternate deletion vector namespace must fail");
+ assert!(error.contains("outside canonical vectors"), "{error}");
+ validate_deletion_vector_namespace(
+ &canonical_request,
+ &canonical_request,
+ &canonical_suppression,
+ &vector,
+ )
+ .expect("canonical deletion vector owns the namespace");
+ validate_deletion_vector_namespace(
+ &canonical_suppression,
+ &canonical_request,
+ &canonical_suppression,
+ &vector,
+ )
+ .expect("canonical deletion suppression vector owns the namespace");
}
#[test]
diff --git a/tools/xtask/src/contract/deletion_authority.rs b/tools/xtask/src/contract/deletion_authority.rs
@@ -2,8 +2,10 @@ use super::DeletionOperationExpectation;
pub(super) const DELETION_CONFORMANCE_VECTOR_RELATIVE: &str =
"contracts/conformance/vectors/deletion/verified_profile.v1.json";
+pub(super) const DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE: &str =
+ "contracts/conformance/vectors/deletion/suppression.v1.json";
-pub(super) const REQUIRED_DELETION_PUBLIC_TYPES: [&str; 18] = [
+pub(super) const REQUIRED_DELETION_PUBLIC_TYPES: [&str; 23] = [
"RadrootsNip01EventWireParts",
"RadrootsEventEnvelope",
"RadrootsSignatureVerifiedEvent",
@@ -22,12 +24,18 @@ pub(super) const REQUIRED_DELETION_PUBLIC_TYPES: [&str; 18] = [
"RadrootsNip09DeletionProjectionError",
"RadrootsAdmittedNip09DeletionRequestEvent",
"RadrootsNip09DeletionAdmissionError",
+ "RadrootsNip09SuppressionOutcome",
+ "RadrootsNip09SuppressionReason",
+ "RadrootsNip09EventReferenceEvidence",
+ "RadrootsNip09AddressReferenceEvidence",
+ "RadrootsNip09SuppressionDecision",
];
-pub(super) const DELETION_OPERATION_EXPECTATIONS: [DeletionOperationExpectation; 3] = [
+pub(super) const DELETION_OPERATION_EXPECTATIONS: [DeletionOperationExpectation; 4] = [
DeletionOperationExpectation {
key: "social_deletion_request_build_authored_draft",
id: "social.deletion_request.build_authored_draft",
+ vector: DELETION_CONFORMANCE_VECTOR_RELATIVE,
inputs: &["RadrootsAuthoredNip09DeletionRequest"],
outputs: &["RadrootsNip01EventWireParts"],
error_class: "encode_error",
@@ -52,6 +60,7 @@ pub(super) const DELETION_OPERATION_EXPECTATIONS: [DeletionOperationExpectation;
DeletionOperationExpectation {
key: "social_deletion_request_project_verified_event",
id: "social.deletion_request.project_verified_event",
+ vector: DELETION_CONFORMANCE_VECTOR_RELATIVE,
inputs: &["RadrootsSignatureVerifiedEvent"],
outputs: &["RadrootsInboundNip09DeletionProjection"],
error_class: "parse_error",
@@ -77,6 +86,7 @@ pub(super) const DELETION_OPERATION_EXPECTATIONS: [DeletionOperationExpectation;
DeletionOperationExpectation {
key: "social_deletion_request_verify_and_admit_event",
id: "social.deletion_request.verify_and_admit_event",
+ vector: DELETION_CONFORMANCE_VECTOR_RELATIVE,
inputs: &["RadrootsEventEnvelope"],
outputs: &["RadrootsAdmittedNip09DeletionRequestEvent"],
error_class: "admission_error",
@@ -99,15 +109,39 @@ pub(super) const DELETION_OPERATION_EXPECTATIONS: [DeletionOperationExpectation;
"social.deletion_request.verify_and_admit_event.invalid",
],
},
+ DeletionOperationExpectation {
+ key: "social_deletion_request_evaluate_suppression",
+ id: "social.deletion_request.evaluate_suppression",
+ vector: DELETION_SUPPRESSION_CONFORMANCE_VECTOR_RELATIVE,
+ inputs: &[
+ "RadrootsSignatureVerifiedEvent",
+ "RadrootsAdmittedNip09DeletionRequestEvent[]",
+ ],
+ outputs: &["RadrootsNip09SuppressionDecision"],
+ error_class: "validation_error",
+ signing: "none",
+ rust_modules: &["crates/event_codec/src/deletion/evaluator.rs"],
+ rust_types: &[
+ "radroots_event_codec::deletion::admission::RadrootsAdmittedNip09DeletionRequestEvent",
+ "radroots_event_codec::deletion::evaluator::RadrootsNip09AddressReferenceEvidence",
+ "radroots_event_codec::deletion::evaluator::RadrootsNip09EventReferenceEvidence",
+ "radroots_event_codec::deletion::evaluator::RadrootsNip09SuppressionDecision",
+ "radroots_event_codec::deletion::evaluator::RadrootsNip09SuppressionOutcome",
+ "radroots_event_codec::deletion::evaluator::RadrootsNip09SuppressionReason",
+ "radroots_event_codec::verification::RadrootsSignatureVerifiedEvent",
+ ],
+ case_kinds: &["social.deletion_request.evaluate_suppression.valid"],
+ },
];
-pub(super) const DELETION_CASE_KINDS: [&str; 6] = [
+pub(super) const DELETION_CASE_KINDS: [&str; 7] = [
"social.deletion_request.build_authored_draft.valid",
"social.deletion_request.build_authored_draft.invalid",
"social.deletion_request.project_verified_event.valid",
"social.deletion_request.project_verified_event.invalid",
"social.deletion_request.verify_and_admit_event.valid",
"social.deletion_request.verify_and_admit_event.invalid",
+ "social.deletion_request.evaluate_suppression.valid",
];
pub(super) const DELETION_AUTHORED_VALID_IDS: [&str; 14] = [
@@ -207,3 +241,38 @@ pub(super) const DELETION_ADMIT_INVALID_IDS: [&str; 5] = [
"nip09_admit_invalid_target",
"nip09_admit_target_missing",
];
+
+pub(super) const DELETION_SUPPRESSION_VALID_IDS: [&str; 32] = [
+ "nip09_suppress_no_requests_visible",
+ "nip09_suppress_same_author_event_reference",
+ "nip09_suppress_same_author_nonmatching_reference",
+ "nip09_suppress_wrong_author_exact_event_reference",
+ "nip09_suppress_event_reference_predates_target",
+ "nip09_suppress_deletion_request_immune_event_reference",
+ "nip09_suppress_deletion_request_immune_mixed_references",
+ "nip09_suppress_address_cutoff_before_target",
+ "nip09_suppress_address_cutoff_equal_target",
+ "nip09_suppress_address_cutoff_after_target",
+ "nip09_suppress_address_wrong_kind",
+ "nip09_suppress_address_wrong_identifier",
+ "nip09_suppress_address_wrong_pubkey",
+ "nip09_suppress_wrong_author_address_reference",
+ "nip09_suppress_replaceable_kind_0",
+ "nip09_suppress_replaceable_kind_3",
+ "nip09_suppress_replaceable_kind_10000",
+ "nip09_suppress_replaceable_kind_19999",
+ "nip09_suppress_addressable_kind_30000_empty_identifier",
+ "nip09_suppress_addressable_kind_39999_opaque_identifier",
+ "nip09_suppress_combined_event_and_address_references",
+ "nip09_suppress_event_reference_with_stale_address",
+ "nip09_suppress_kind_advisory_diagnostics_ignored",
+ "nip09_suppress_duplicate_raw_targets_deduplicated",
+ "nip09_suppress_max_address_cutoff_forward_order",
+ "nip09_suppress_max_address_cutoff_reverse_order",
+ "nip09_suppress_later_revision_survives_address_cutoff",
+ "nip09_suppress_equal_max_cutoff_uses_lowest_request_id",
+ "nip09_suppress_multiple_event_references_use_lowest_request_id",
+ "nip09_suppress_repeated_request_is_idempotent",
+ "nip09_suppress_unauthorized_event_plus_stale_authorized_address",
+ "nip09_suppress_exact_event_deletes_later_replacement",
+];