lib

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

commit 79a1e89995f8c397002f64d1d706714019211d1a
parent eb9246e86c9a72afde35ecb2fd994572250fcf07
Author: triesap <tyson@radroots.org>
Date:   Sun,  5 Jul 2026 20:00:50 +0000

events-codec: harden knowledge manifest contracts

Diffstat:
Mcontracts/conformance/vectors/knowledge/manifest_and_decode.v1.json | 5++++-
Dcontracts/knowledge/knowledge_event_contract_manifest.v1.json | 636-------------------------------------------------------------------------------
Dcontracts/knowledge/knowledge_event_contract_manifest.v1.sha256 | 1-
Acontracts/knowledge/knowledge_event_contract_manifest.v2.json | 669+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontracts/knowledge/knowledge_event_contract_manifest.v2.sha256 | 1+
Mcrates/events/src/contract.rs | 130+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mcrates/events_codec/src/manifest.rs | 25+++++++++++++++++++++++--
Mcrates/events_codec/tests/knowledge_manifest.rs | 53++++++++++++++++++++++++++++++++++++++++++++++++++---
Mtools/xtask/src/contract.rs | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
9 files changed, 996 insertions(+), 664 deletions(-)

diff --git a/contracts/conformance/vectors/knowledge/manifest_and_decode.v1.json b/contracts/conformance/vectors/knowledge/manifest_and_decode.v1.json @@ -9,7 +9,7 @@ "registry": "radroots_event_contract_registry_v1" }, "expected": { - "schema_version": 1, + "schema_version": 2, "required_fields": [ "schema_version", "registry_version", @@ -26,6 +26,9 @@ "tag_contracts", "codec_support", "sdk_builder_support", + "sdk_draft_support", + "wasm_tag_builder_support", + "wasm_verified_decode_support", "deprecated", "replaced_by", "introduced_at" diff --git a/contracts/knowledge/knowledge_event_contract_manifest.v1.json b/contracts/knowledge/knowledge_event_contract_manifest.v1.json @@ -1,636 +0,0 @@ -{ - "schema_version": 1, - "registry_version": 1, - "radroots_events_version": "0.1.0-alpha.2", - "radroots_events_codec_version": "0.1.0-alpha.2", - "contract_count": 11, - "contracts": [ - { - "contract_id": "radroots.knowledge.change_proposal.v1", - "kind": 3464, - "class": "regular", - "standard": "radroots", - "stability": "stable", - "privacy": "public", - "author_role": "any", - "content_schema": "json_object", - "payload_type": "RadrootsKnowledgeChangeProposal", - "discriminators": [ - { - "type": "tag_equals", - "name": "contract", - "value": "radroots.knowledge.change_proposal.v1" - } - ], - "tag_contracts": [ - { - "name": "contract", - "cardinality": "required_one", - "semantic": "contract", - "value_type": "contract_id", - "relay_indexed": false - }, - { - "name": "evidence", - "cardinality": "optional_many", - "semantic": "evidence", - "value_type": "event_pointer", - "relay_indexed": false - } - ], - "reducers": [ - "knowledge_projection" - ], - "codec_support": { - "encode": true, - "decode": true, - "contract_validation": true, - "verified_decode": true, - "verified_decode_requires_nostr": true - }, - "sdk_builder_support": false, - "deprecated": false, - "replaced_by": null, - "introduced_at": "0.1.0-alpha.2" - }, - { - "contract_id": "radroots.knowledge.claim.v1", - "kind": 3460, - "class": "regular", - "standard": "radroots", - "stability": "stable", - "privacy": "public", - "author_role": "any", - "content_schema": "json_object", - "payload_type": "RadrootsKnowledgeClaim", - "discriminators": [ - { - "type": "tag_equals", - "name": "contract", - "value": "radroots.knowledge.claim.v1" - } - ], - "tag_contracts": [ - { - "name": "contract", - "cardinality": "required_one", - "semantic": "contract", - "value_type": "contract_id", - "relay_indexed": false - }, - { - "name": "t", - "cardinality": "optional_many", - "semantic": "topic", - "value_type": "text", - "relay_indexed": true - }, - { - "name": "source", - "cardinality": "optional_many", - "semantic": "source", - "value_type": "event_pointer", - "relay_indexed": false - }, - { - "name": "citation", - "cardinality": "optional_many", - "semantic": "citation", - "value_type": "sha256", - "relay_indexed": false - } - ], - "reducers": [ - "knowledge_projection" - ], - "codec_support": { - "encode": true, - "decode": true, - "contract_validation": true, - "verified_decode": true, - "verified_decode_requires_nostr": true - }, - "sdk_builder_support": false, - "deprecated": false, - "replaced_by": null, - "introduced_at": "0.1.0-alpha.2" - }, - { - "contract_id": "radroots.knowledge.contribution_attestation.v1", - "kind": 3465, - "class": "regular", - "standard": "radroots", - "stability": "stable", - "privacy": "public", - "author_role": "any", - "content_schema": "json_object", - "payload_type": "RadrootsContributionAttestation", - "discriminators": [ - { - "type": "tag_equals", - "name": "contract", - "value": "radroots.knowledge.contribution_attestation.v1" - } - ], - "tag_contracts": [ - { - "name": "contract", - "cardinality": "required_one", - "semantic": "contract", - "value_type": "contract_id", - "relay_indexed": false - }, - { - "name": "evidence", - "cardinality": "optional_many", - "semantic": "evidence", - "value_type": "event_pointer", - "relay_indexed": false - } - ], - "reducers": [ - "knowledge_projection" - ], - "codec_support": { - "encode": true, - "decode": true, - "contract_validation": true, - "verified_decode": true, - "verified_decode_requires_nostr": true - }, - "sdk_builder_support": false, - "deprecated": false, - "replaced_by": null, - "introduced_at": "0.1.0-alpha.2" - }, - { - "contract_id": "radroots.knowledge.evidence_bounty.v1", - "kind": 30451, - "class": "addressable", - "standard": "radroots", - "stability": "stable", - "privacy": "public", - "author_role": "any", - "content_schema": "json_object", - "payload_type": "RadrootsEvidenceBounty", - "discriminators": [ - { - "type": "tag_equals", - "name": "contract", - "value": "radroots.knowledge.evidence_bounty.v1" - } - ], - "tag_contracts": [ - { - "name": "d", - "cardinality": "required_one", - "semantic": "identifier", - "value_type": "d_tag", - "relay_indexed": true - }, - { - "name": "contract", - "cardinality": "required_one", - "semantic": "contract", - "value_type": "contract_id", - "relay_indexed": false - }, - { - "name": "t", - "cardinality": "optional_many", - "semantic": "topic", - "value_type": "text", - "relay_indexed": true - }, - { - "name": "evidence", - "cardinality": "optional_many", - "semantic": "evidence", - "value_type": "event_pointer", - "relay_indexed": false - } - ], - "reducers": [ - "knowledge_projection" - ], - "codec_support": { - "encode": true, - "decode": true, - "contract_validation": true, - "verified_decode": true, - "verified_decode_requires_nostr": true - }, - "sdk_builder_support": false, - "deprecated": false, - "replaced_by": null, - "introduced_at": "0.1.0-alpha.2" - }, - { - "contract_id": "radroots.knowledge.field_report.v1", - "kind": 3463, - "class": "regular", - "standard": "radroots", - "stability": "stable", - "privacy": "public", - "author_role": "any", - "content_schema": "json_object", - "payload_type": "RadrootsKnowledgeFieldReport", - "discriminators": [ - { - "type": "tag_equals", - "name": "contract", - "value": "radroots.knowledge.field_report.v1" - } - ], - "tag_contracts": [ - { - "name": "contract", - "cardinality": "required_one", - "semantic": "contract", - "value_type": "contract_id", - "relay_indexed": false - }, - { - "name": "t", - "cardinality": "optional_many", - "semantic": "topic", - "value_type": "text", - "relay_indexed": true - }, - { - "name": "g", - "cardinality": "optional_one", - "semantic": "geohash", - "value_type": "geohash", - "relay_indexed": true - }, - { - "name": "evidence", - "cardinality": "optional_many", - "semantic": "evidence", - "value_type": "event_pointer", - "relay_indexed": false - } - ], - "reducers": [ - "knowledge_projection" - ], - "codec_support": { - "encode": true, - "decode": true, - "contract_validation": true, - "verified_decode": true, - "verified_decode_requires_nostr": true - }, - "sdk_builder_support": false, - "deprecated": false, - "replaced_by": null, - "introduced_at": "0.1.0-alpha.2" - }, - { - "contract_id": "radroots.knowledge.relation.v1", - "kind": 3461, - "class": "regular", - "standard": "radroots", - "stability": "stable", - "privacy": "public", - "author_role": "any", - "content_schema": "json_object", - "payload_type": "RadrootsKnowledgeRelation", - "discriminators": [ - { - "type": "tag_equals", - "name": "contract", - "value": "radroots.knowledge.relation.v1" - } - ], - "tag_contracts": [ - { - "name": "contract", - "cardinality": "required_one", - "semantic": "contract", - "value_type": "contract_id", - "relay_indexed": false - }, - { - "name": "t", - "cardinality": "optional_many", - "semantic": "topic", - "value_type": "text", - "relay_indexed": true - }, - { - "name": "source", - "cardinality": "optional_many", - "semantic": "source", - "value_type": "event_pointer", - "relay_indexed": false - } - ], - "reducers": [ - "knowledge_projection" - ], - "codec_support": { - "encode": true, - "decode": true, - "contract_validation": true, - "verified_decode": true, - "verified_decode_requires_nostr": true - }, - "sdk_builder_support": false, - "deprecated": false, - "replaced_by": null, - "introduced_at": "0.1.0-alpha.2" - }, - { - "contract_id": "radroots.knowledge.review.v1", - "kind": 3462, - "class": "regular", - "standard": "radroots", - "stability": "stable", - "privacy": "public", - "author_role": "any", - "content_schema": "json_object", - "payload_type": "RadrootsKnowledgeReview", - "discriminators": [ - { - "type": "tag_equals", - "name": "contract", - "value": "radroots.knowledge.review.v1" - } - ], - "tag_contracts": [ - { - "name": "contract", - "cardinality": "required_one", - "semantic": "contract", - "value_type": "contract_id", - "relay_indexed": false - }, - { - "name": "review_target", - "cardinality": "required_one", - "semantic": "review_target", - "value_type": "event_pointer", - "relay_indexed": false - }, - { - "name": "evidence", - "cardinality": "optional_many", - "semantic": "evidence", - "value_type": "event_pointer", - "relay_indexed": false - } - ], - "reducers": [ - "knowledge_projection" - ], - "codec_support": { - "encode": true, - "decode": true, - "contract_validation": true, - "verified_decode": true, - "verified_decode_requires_nostr": true - }, - "sdk_builder_support": false, - "deprecated": false, - "replaced_by": null, - "introduced_at": "0.1.0-alpha.2" - }, - { - "contract_id": "radroots.knowledge.source.v1", - "kind": 30450, - "class": "addressable", - "standard": "radroots", - "stability": "stable", - "privacy": "public", - "author_role": "any", - "content_schema": "json_object", - "payload_type": "RadrootsKnowledgeSource", - "discriminators": [ - { - "type": "tag_equals", - "name": "contract", - "value": "radroots.knowledge.source.v1" - } - ], - "tag_contracts": [ - { - "name": "d", - "cardinality": "required_one", - "semantic": "identifier", - "value_type": "d_tag", - "relay_indexed": true - }, - { - "name": "contract", - "cardinality": "required_one", - "semantic": "contract", - "value_type": "contract_id", - "relay_indexed": false - }, - { - "name": "t", - "cardinality": "optional_many", - "semantic": "topic", - "value_type": "text", - "relay_indexed": true - }, - { - "name": "source", - "cardinality": "optional_many", - "semantic": "source", - "value_type": "event_pointer", - "relay_indexed": false - } - ], - "reducers": [ - "knowledge_projection" - ], - "codec_support": { - "encode": true, - "decode": true, - "contract_validation": true, - "verified_decode": true, - "verified_decode_requires_nostr": true - }, - "sdk_builder_support": false, - "deprecated": false, - "replaced_by": null, - "introduced_at": "0.1.0-alpha.2" - }, - { - "contract_id": "radroots.wiki.article.v1", - "kind": 30818, - "class": "addressable", - "standard": "nip54", - "stability": "stable", - "privacy": "public", - "author_role": "any", - "content_schema": "djot", - "payload_type": "RadrootsWikiArticle", - "discriminators": [ - { - "type": "kind_only" - } - ], - "tag_contracts": [ - { - "name": "d", - "cardinality": "required_one", - "semantic": "identifier", - "value_type": "d_tag", - "relay_indexed": true - }, - { - "name": "title", - "cardinality": "optional_one", - "semantic": "title", - "value_type": "text", - "relay_indexed": false - }, - { - "name": "summary", - "cardinality": "optional_one", - "semantic": "summary", - "value_type": "text", - "relay_indexed": false - }, - { - "name": "published_at", - "cardinality": "optional_one", - "semantic": "published_at", - "value_type": "unix_timestamp", - "relay_indexed": false - }, - { - "name": "t", - "cardinality": "optional_many", - "semantic": "topic", - "value_type": "text", - "relay_indexed": true - } - ], - "reducers": [ - "knowledge_projection" - ], - "codec_support": { - "encode": true, - "decode": true, - "contract_validation": true, - "verified_decode": true, - "verified_decode_requires_nostr": true - }, - "sdk_builder_support": false, - "deprecated": false, - "replaced_by": null, - "introduced_at": "0.1.0-alpha.2" - }, - { - "contract_id": "radroots.wiki.merge_request.v1", - "kind": 818, - "class": "regular", - "standard": "nip54", - "stability": "stable", - "privacy": "public", - "author_role": "any", - "content_schema": "json_object", - "payload_type": "RadrootsWikiMergeRequest", - "discriminators": [ - { - "type": "kind_only" - } - ], - "tag_contracts": [ - { - "name": "a", - "cardinality": "required_one", - "semantic": "addressable_coordinate", - "value_type": "addressable_coordinate", - "relay_indexed": true - }, - { - "name": "p", - "cardinality": "required_one", - "semantic": "counterparty", - "value_type": "public_key", - "relay_indexed": true - }, - { - "name": "e", - "cardinality": "required_one", - "semantic": "source", - "value_type": "event_id", - "relay_indexed": true - }, - { - "name": "e", - "cardinality": "optional_one", - "semantic": "previous_event", - "value_type": "event_id", - "relay_indexed": true - } - ], - "reducers": [ - "knowledge_projection" - ], - "codec_support": { - "encode": true, - "decode": true, - "contract_validation": true, - "verified_decode": true, - "verified_decode_requires_nostr": true - }, - "sdk_builder_support": false, - "deprecated": false, - "replaced_by": null, - "introduced_at": "0.1.0-alpha.2" - }, - { - "contract_id": "radroots.wiki.redirect.v1", - "kind": 30819, - "class": "addressable", - "standard": "nip54", - "stability": "stable", - "privacy": "public", - "author_role": "any", - "content_schema": "empty", - "payload_type": "RadrootsWikiRedirect", - "discriminators": [ - { - "type": "kind_only" - } - ], - "tag_contracts": [ - { - "name": "d", - "cardinality": "required_one", - "semantic": "identifier", - "value_type": "d_tag", - "relay_indexed": true - }, - { - "name": "a", - "cardinality": "required_one", - "semantic": "addressable_coordinate", - "value_type": "addressable_coordinate", - "relay_indexed": true - } - ], - "reducers": [ - "knowledge_projection" - ], - "codec_support": { - "encode": true, - "decode": true, - "contract_validation": true, - "verified_decode": true, - "verified_decode_requires_nostr": true - }, - "sdk_builder_support": false, - "deprecated": false, - "replaced_by": null, - "introduced_at": "0.1.0-alpha.2" - } - ] -} diff --git a/contracts/knowledge/knowledge_event_contract_manifest.v1.sha256 b/contracts/knowledge/knowledge_event_contract_manifest.v1.sha256 @@ -1 +0,0 @@ -8e685748e731767c6a81ce0721a674567dd8010e800d21cd5e12688da1b73757 diff --git a/contracts/knowledge/knowledge_event_contract_manifest.v2.json b/contracts/knowledge/knowledge_event_contract_manifest.v2.json @@ -0,0 +1,669 @@ +{ + "schema_version": 2, + "registry_version": 1, + "radroots_events_version": "0.1.0-alpha.2", + "radroots_events_codec_version": "0.1.0-alpha.2", + "contract_count": 11, + "contracts": [ + { + "contract_id": "radroots.knowledge.change_proposal.v1", + "kind": 3464, + "class": "regular", + "standard": "radroots", + "stability": "experimental", + "privacy": "public", + "author_role": "any", + "content_schema": "json_object", + "payload_type": "RadrootsKnowledgeChangeProposal", + "discriminators": [ + { + "type": "tag_equals", + "name": "contract", + "value": "radroots.knowledge.change_proposal.v1" + } + ], + "tag_contracts": [ + { + "name": "contract", + "cardinality": "required_one", + "semantic": "contract", + "value_type": "contract_id", + "relay_indexed": false + }, + { + "name": "evidence", + "cardinality": "optional_many", + "semantic": "evidence", + "value_type": "event_pointer", + "relay_indexed": false + } + ], + "reducers": [ + "knowledge_projection" + ], + "codec_support": { + "encode": true, + "decode": true, + "contract_validation": true, + "verified_decode": true, + "verified_decode_requires_nostr": true + }, + "sdk_builder_support": false, + "sdk_draft_support": false, + "wasm_tag_builder_support": false, + "wasm_verified_decode_support": true, + "deprecated": false, + "replaced_by": null, + "introduced_at": "0.1.0-alpha.2" + }, + { + "contract_id": "radroots.knowledge.claim.v1", + "kind": 3460, + "class": "regular", + "standard": "radroots", + "stability": "experimental", + "privacy": "public", + "author_role": "any", + "content_schema": "json_object", + "payload_type": "RadrootsKnowledgeClaim", + "discriminators": [ + { + "type": "tag_equals", + "name": "contract", + "value": "radroots.knowledge.claim.v1" + } + ], + "tag_contracts": [ + { + "name": "contract", + "cardinality": "required_one", + "semantic": "contract", + "value_type": "contract_id", + "relay_indexed": false + }, + { + "name": "t", + "cardinality": "optional_many", + "semantic": "topic", + "value_type": "text", + "relay_indexed": true + }, + { + "name": "source", + "cardinality": "optional_many", + "semantic": "source", + "value_type": "event_pointer", + "relay_indexed": false + }, + { + "name": "citation", + "cardinality": "optional_many", + "semantic": "citation", + "value_type": "sha256", + "relay_indexed": false + } + ], + "reducers": [ + "knowledge_projection" + ], + "codec_support": { + "encode": true, + "decode": true, + "contract_validation": true, + "verified_decode": true, + "verified_decode_requires_nostr": true + }, + "sdk_builder_support": true, + "sdk_draft_support": true, + "wasm_tag_builder_support": true, + "wasm_verified_decode_support": true, + "deprecated": false, + "replaced_by": null, + "introduced_at": "0.1.0-alpha.2" + }, + { + "contract_id": "radroots.knowledge.contribution_attestation.v1", + "kind": 3465, + "class": "regular", + "standard": "radroots", + "stability": "experimental", + "privacy": "public", + "author_role": "any", + "content_schema": "json_object", + "payload_type": "RadrootsContributionAttestation", + "discriminators": [ + { + "type": "tag_equals", + "name": "contract", + "value": "radroots.knowledge.contribution_attestation.v1" + } + ], + "tag_contracts": [ + { + "name": "contract", + "cardinality": "required_one", + "semantic": "contract", + "value_type": "contract_id", + "relay_indexed": false + }, + { + "name": "evidence", + "cardinality": "optional_many", + "semantic": "evidence", + "value_type": "event_pointer", + "relay_indexed": false + } + ], + "reducers": [ + "knowledge_projection" + ], + "codec_support": { + "encode": true, + "decode": true, + "contract_validation": true, + "verified_decode": true, + "verified_decode_requires_nostr": true + }, + "sdk_builder_support": false, + "sdk_draft_support": false, + "wasm_tag_builder_support": false, + "wasm_verified_decode_support": true, + "deprecated": false, + "replaced_by": null, + "introduced_at": "0.1.0-alpha.2" + }, + { + "contract_id": "radroots.knowledge.evidence_bounty.v1", + "kind": 30451, + "class": "addressable", + "standard": "radroots", + "stability": "experimental", + "privacy": "public", + "author_role": "any", + "content_schema": "json_object", + "payload_type": "RadrootsEvidenceBounty", + "discriminators": [ + { + "type": "tag_equals", + "name": "contract", + "value": "radroots.knowledge.evidence_bounty.v1" + } + ], + "tag_contracts": [ + { + "name": "d", + "cardinality": "required_one", + "semantic": "identifier", + "value_type": "d_tag", + "relay_indexed": true + }, + { + "name": "contract", + "cardinality": "required_one", + "semantic": "contract", + "value_type": "contract_id", + "relay_indexed": false + }, + { + "name": "t", + "cardinality": "optional_many", + "semantic": "topic", + "value_type": "text", + "relay_indexed": true + }, + { + "name": "evidence", + "cardinality": "optional_many", + "semantic": "evidence", + "value_type": "event_pointer", + "relay_indexed": false + } + ], + "reducers": [ + "knowledge_projection" + ], + "codec_support": { + "encode": true, + "decode": true, + "contract_validation": true, + "verified_decode": true, + "verified_decode_requires_nostr": true + }, + "sdk_builder_support": false, + "sdk_draft_support": false, + "wasm_tag_builder_support": false, + "wasm_verified_decode_support": true, + "deprecated": false, + "replaced_by": null, + "introduced_at": "0.1.0-alpha.2" + }, + { + "contract_id": "radroots.knowledge.field_report.v1", + "kind": 3463, + "class": "regular", + "standard": "radroots", + "stability": "experimental", + "privacy": "public", + "author_role": "any", + "content_schema": "json_object", + "payload_type": "RadrootsKnowledgeFieldReport", + "discriminators": [ + { + "type": "tag_equals", + "name": "contract", + "value": "radroots.knowledge.field_report.v1" + } + ], + "tag_contracts": [ + { + "name": "contract", + "cardinality": "required_one", + "semantic": "contract", + "value_type": "contract_id", + "relay_indexed": false + }, + { + "name": "t", + "cardinality": "optional_many", + "semantic": "topic", + "value_type": "text", + "relay_indexed": true + }, + { + "name": "g", + "cardinality": "optional_one", + "semantic": "geohash", + "value_type": "geohash", + "relay_indexed": true + }, + { + "name": "evidence", + "cardinality": "optional_many", + "semantic": "evidence", + "value_type": "event_pointer", + "relay_indexed": false + } + ], + "reducers": [ + "knowledge_projection" + ], + "codec_support": { + "encode": true, + "decode": true, + "contract_validation": true, + "verified_decode": true, + "verified_decode_requires_nostr": true + }, + "sdk_builder_support": true, + "sdk_draft_support": true, + "wasm_tag_builder_support": true, + "wasm_verified_decode_support": true, + "deprecated": false, + "replaced_by": null, + "introduced_at": "0.1.0-alpha.2" + }, + { + "contract_id": "radroots.knowledge.relation.v1", + "kind": 3461, + "class": "regular", + "standard": "radroots", + "stability": "experimental", + "privacy": "public", + "author_role": "any", + "content_schema": "json_object", + "payload_type": "RadrootsKnowledgeRelation", + "discriminators": [ + { + "type": "tag_equals", + "name": "contract", + "value": "radroots.knowledge.relation.v1" + } + ], + "tag_contracts": [ + { + "name": "contract", + "cardinality": "required_one", + "semantic": "contract", + "value_type": "contract_id", + "relay_indexed": false + }, + { + "name": "t", + "cardinality": "optional_many", + "semantic": "topic", + "value_type": "text", + "relay_indexed": true + }, + { + "name": "source", + "cardinality": "optional_many", + "semantic": "source", + "value_type": "event_pointer", + "relay_indexed": false + } + ], + "reducers": [ + "knowledge_projection" + ], + "codec_support": { + "encode": true, + "decode": true, + "contract_validation": true, + "verified_decode": true, + "verified_decode_requires_nostr": true + }, + "sdk_builder_support": true, + "sdk_draft_support": true, + "wasm_tag_builder_support": true, + "wasm_verified_decode_support": true, + "deprecated": false, + "replaced_by": null, + "introduced_at": "0.1.0-alpha.2" + }, + { + "contract_id": "radroots.knowledge.review.v1", + "kind": 3462, + "class": "regular", + "standard": "radroots", + "stability": "experimental", + "privacy": "public", + "author_role": "any", + "content_schema": "json_object", + "payload_type": "RadrootsKnowledgeReview", + "discriminators": [ + { + "type": "tag_equals", + "name": "contract", + "value": "radroots.knowledge.review.v1" + } + ], + "tag_contracts": [ + { + "name": "contract", + "cardinality": "required_one", + "semantic": "contract", + "value_type": "contract_id", + "relay_indexed": false + }, + { + "name": "review_target", + "cardinality": "required_one", + "semantic": "review_target", + "value_type": "event_pointer", + "relay_indexed": false + }, + { + "name": "evidence", + "cardinality": "optional_many", + "semantic": "evidence", + "value_type": "event_pointer", + "relay_indexed": false + } + ], + "reducers": [ + "knowledge_projection" + ], + "codec_support": { + "encode": true, + "decode": true, + "contract_validation": true, + "verified_decode": true, + "verified_decode_requires_nostr": true + }, + "sdk_builder_support": true, + "sdk_draft_support": true, + "wasm_tag_builder_support": true, + "wasm_verified_decode_support": true, + "deprecated": false, + "replaced_by": null, + "introduced_at": "0.1.0-alpha.2" + }, + { + "contract_id": "radroots.knowledge.source.v1", + "kind": 30450, + "class": "addressable", + "standard": "radroots", + "stability": "experimental", + "privacy": "public", + "author_role": "any", + "content_schema": "json_object", + "payload_type": "RadrootsKnowledgeSource", + "discriminators": [ + { + "type": "tag_equals", + "name": "contract", + "value": "radroots.knowledge.source.v1" + } + ], + "tag_contracts": [ + { + "name": "d", + "cardinality": "required_one", + "semantic": "identifier", + "value_type": "d_tag", + "relay_indexed": true + }, + { + "name": "contract", + "cardinality": "required_one", + "semantic": "contract", + "value_type": "contract_id", + "relay_indexed": false + }, + { + "name": "t", + "cardinality": "optional_many", + "semantic": "topic", + "value_type": "text", + "relay_indexed": true + }, + { + "name": "source", + "cardinality": "optional_many", + "semantic": "source", + "value_type": "event_pointer", + "relay_indexed": false + } + ], + "reducers": [ + "knowledge_projection" + ], + "codec_support": { + "encode": true, + "decode": true, + "contract_validation": true, + "verified_decode": true, + "verified_decode_requires_nostr": true + }, + "sdk_builder_support": true, + "sdk_draft_support": true, + "wasm_tag_builder_support": true, + "wasm_verified_decode_support": true, + "deprecated": false, + "replaced_by": null, + "introduced_at": "0.1.0-alpha.2" + }, + { + "contract_id": "radroots.wiki.article.v1", + "kind": 30818, + "class": "addressable", + "standard": "nip54", + "stability": "experimental", + "privacy": "public", + "author_role": "any", + "content_schema": "djot", + "payload_type": "RadrootsWikiArticle", + "discriminators": [ + { + "type": "kind_only" + } + ], + "tag_contracts": [ + { + "name": "d", + "cardinality": "required_one", + "semantic": "identifier", + "value_type": "d_tag", + "relay_indexed": true + }, + { + "name": "title", + "cardinality": "optional_one", + "semantic": "title", + "value_type": "text", + "relay_indexed": false + }, + { + "name": "summary", + "cardinality": "optional_one", + "semantic": "summary", + "value_type": "text", + "relay_indexed": false + }, + { + "name": "published_at", + "cardinality": "optional_one", + "semantic": "published_at", + "value_type": "unix_timestamp", + "relay_indexed": false + }, + { + "name": "t", + "cardinality": "optional_many", + "semantic": "topic", + "value_type": "text", + "relay_indexed": true + } + ], + "reducers": [ + "knowledge_projection" + ], + "codec_support": { + "encode": true, + "decode": true, + "contract_validation": true, + "verified_decode": true, + "verified_decode_requires_nostr": true + }, + "sdk_builder_support": true, + "sdk_draft_support": true, + "wasm_tag_builder_support": true, + "wasm_verified_decode_support": true, + "deprecated": false, + "replaced_by": null, + "introduced_at": "0.1.0-alpha.2" + }, + { + "contract_id": "radroots.wiki.merge_request.v1", + "kind": 818, + "class": "regular", + "standard": "nip54", + "stability": "experimental", + "privacy": "public", + "author_role": "any", + "content_schema": "plain_text", + "payload_type": "RadrootsWikiMergeRequest", + "discriminators": [ + { + "type": "kind_only" + } + ], + "tag_contracts": [ + { + "name": "a", + "cardinality": "required_one", + "semantic": "addressable_coordinate", + "value_type": "addressable_coordinate", + "relay_indexed": true + }, + { + "name": "p", + "cardinality": "required_one", + "semantic": "counterparty", + "value_type": "public_key", + "relay_indexed": true + }, + { + "name": "e", + "cardinality": "required_one", + "semantic": "source", + "value_type": "event_id", + "relay_indexed": true + }, + { + "name": "e", + "cardinality": "optional_one", + "semantic": "previous_event", + "value_type": "event_id", + "relay_indexed": true + } + ], + "reducers": [ + "knowledge_projection" + ], + "codec_support": { + "encode": true, + "decode": true, + "contract_validation": true, + "verified_decode": true, + "verified_decode_requires_nostr": true + }, + "sdk_builder_support": true, + "sdk_draft_support": true, + "wasm_tag_builder_support": true, + "wasm_verified_decode_support": true, + "deprecated": false, + "replaced_by": null, + "introduced_at": "0.1.0-alpha.2" + }, + { + "contract_id": "radroots.wiki.redirect.v1", + "kind": 30819, + "class": "addressable", + "standard": "nip54", + "stability": "experimental", + "privacy": "public", + "author_role": "any", + "content_schema": "empty", + "payload_type": "RadrootsWikiRedirect", + "discriminators": [ + { + "type": "kind_only" + } + ], + "tag_contracts": [ + { + "name": "d", + "cardinality": "required_one", + "semantic": "identifier", + "value_type": "d_tag", + "relay_indexed": true + }, + { + "name": "a", + "cardinality": "required_one", + "semantic": "addressable_coordinate", + "value_type": "addressable_coordinate", + "relay_indexed": true + } + ], + "reducers": [ + "knowledge_projection" + ], + "codec_support": { + "encode": true, + "decode": true, + "contract_validation": true, + "verified_decode": true, + "verified_decode_requires_nostr": true + }, + "sdk_builder_support": true, + "sdk_draft_support": true, + "wasm_tag_builder_support": true, + "wasm_verified_decode_support": true, + "deprecated": false, + "replaced_by": null, + "introduced_at": "0.1.0-alpha.2" + } + ] +} diff --git a/contracts/knowledge/knowledge_event_contract_manifest.v2.sha256 b/contracts/knowledge/knowledge_event_contract_manifest.v2.sha256 @@ -0,0 +1 @@ +8249f0d85f0f618ed5c7bd2088171df1c80bb2cd6aef9a1577cd78b46215953e diff --git a/crates/events/src/contract.rs b/crates/events/src/contract.rs @@ -778,7 +778,7 @@ macro_rules! kind_contract { }; } -macro_rules! event_contract { +macro_rules! event_contract_with_stability { ( $id:literal, $kind:expr, @@ -790,7 +790,8 @@ macro_rules! event_contract { $content_schema:expr, $discriminator:expr, $tags:expr, - $reducers:expr $(,)? + $reducers:expr, + $stability:expr $(,)? ) => { RadrootsEventContract { id: $id, @@ -798,7 +799,7 @@ macro_rules! event_contract { name: $name, payload_type: $payload_type, class: $class, - stability: RadrootsEventStability::Stable, + stability: $stability, privacy: $standard_privacy, author_role: $author_role, content_schema: $content_schema, @@ -809,6 +810,68 @@ macro_rules! event_contract { }; } +macro_rules! event_contract { + ( + $id:literal, + $kind:expr, + $name:literal, + $payload_type:literal, + $class:expr, + $standard_privacy:expr, + $author_role:expr, + $content_schema:expr, + $discriminator:expr, + $tags:expr, + $reducers:expr $(,)? + ) => { + event_contract_with_stability!( + $id, + $kind, + $name, + $payload_type, + $class, + $standard_privacy, + $author_role, + $content_schema, + $discriminator, + $tags, + $reducers, + RadrootsEventStability::Stable + ) + }; +} + +macro_rules! experimental_event_contract { + ( + $id:literal, + $kind:expr, + $name:literal, + $payload_type:literal, + $class:expr, + $standard_privacy:expr, + $author_role:expr, + $content_schema:expr, + $discriminator:expr, + $tags:expr, + $reducers:expr $(,)? + ) => { + event_contract_with_stability!( + $id, + $kind, + $name, + $payload_type, + $class, + $standard_privacy, + $author_role, + $content_schema, + $discriminator, + $tags, + $reducers, + RadrootsEventStability::Experimental + ) + }; +} + static ALL_KIND_CONTRACTS: &[RadrootsKindContract] = &[ kind_contract!( KIND_PROFILE, @@ -2376,7 +2439,7 @@ static ALL_EVENT_CONTRACTS: &[RadrootsEventContract] = &[ ARTICLE_TAGS, SOCIAL_REDUCERS ), - event_contract!( + experimental_event_contract!( "radroots.wiki.merge_request.v1", KIND_WIKI_MERGE_REQUEST, "Wiki Merge Request", @@ -2389,7 +2452,7 @@ static ALL_EVENT_CONTRACTS: &[RadrootsEventContract] = &[ WIKI_MERGE_REQUEST_TAGS, KNOWLEDGE_REDUCERS ), - event_contract!( + experimental_event_contract!( "radroots.wiki.article.v1", KIND_WIKI_ARTICLE, "Wiki Article", @@ -2402,7 +2465,7 @@ static ALL_EVENT_CONTRACTS: &[RadrootsEventContract] = &[ WIKI_ARTICLE_TAGS, KNOWLEDGE_REDUCERS ), - event_contract!( + experimental_event_contract!( "radroots.wiki.redirect.v1", KIND_WIKI_REDIRECT, "Wiki Redirect", @@ -2623,7 +2686,7 @@ static ALL_EVENT_CONTRACTS: &[RadrootsEventContract] = &[ LISTING_TAGS, LISTING_REDUCERS ), - event_contract!( + experimental_event_contract!( "radroots.knowledge.source.v1", KIND_KNOWLEDGE_SOURCE, "Knowledge Source", @@ -2639,7 +2702,7 @@ static ALL_EVENT_CONTRACTS: &[RadrootsEventContract] = &[ KNOWLEDGE_SOURCE_TAGS, KNOWLEDGE_REDUCERS ), - event_contract!( + experimental_event_contract!( "radroots.knowledge.evidence_bounty.v1", KIND_EVIDENCE_BOUNTY, "Evidence Bounty", @@ -2655,7 +2718,7 @@ static ALL_EVENT_CONTRACTS: &[RadrootsEventContract] = &[ EVIDENCE_BOUNTY_TAGS, KNOWLEDGE_REDUCERS ), - event_contract!( + experimental_event_contract!( "radroots.knowledge.claim.v1", KIND_KNOWLEDGE_CLAIM, "Knowledge Claim", @@ -2671,7 +2734,7 @@ static ALL_EVENT_CONTRACTS: &[RadrootsEventContract] = &[ KNOWLEDGE_CLAIM_TAGS, KNOWLEDGE_REDUCERS ), - event_contract!( + experimental_event_contract!( "radroots.knowledge.relation.v1", KIND_KNOWLEDGE_RELATION, "Knowledge Relation", @@ -2687,7 +2750,7 @@ static ALL_EVENT_CONTRACTS: &[RadrootsEventContract] = &[ KNOWLEDGE_RELATION_TAGS, KNOWLEDGE_REDUCERS ), - event_contract!( + experimental_event_contract!( "radroots.knowledge.review.v1", KIND_KNOWLEDGE_REVIEW, "Knowledge Review", @@ -2703,7 +2766,7 @@ static ALL_EVENT_CONTRACTS: &[RadrootsEventContract] = &[ KNOWLEDGE_REVIEW_TAGS, KNOWLEDGE_REDUCERS ), - event_contract!( + experimental_event_contract!( "radroots.knowledge.field_report.v1", KIND_KNOWLEDGE_FIELD_REPORT, "Knowledge Field Report", @@ -2719,7 +2782,7 @@ static ALL_EVENT_CONTRACTS: &[RadrootsEventContract] = &[ KNOWLEDGE_FIELD_REPORT_TAGS, KNOWLEDGE_REDUCERS ), - event_contract!( + experimental_event_contract!( "radroots.knowledge.change_proposal.v1", KIND_KNOWLEDGE_CHANGE_PROPOSAL, "Knowledge Change Proposal", @@ -2735,7 +2798,7 @@ static ALL_EVENT_CONTRACTS: &[RadrootsEventContract] = &[ KNOWLEDGE_CHANGE_PROPOSAL_TAGS, KNOWLEDGE_REDUCERS ), - event_contract!( + experimental_event_contract!( "radroots.knowledge.contribution_attestation.v1", KIND_CONTRIBUTION_ATTESTATION, "Contribution Attestation", @@ -3070,11 +3133,10 @@ pub fn event_contract(id: &str) -> Option<&'static RadrootsEventContract> { .find(|contract| contract.id == id) } -pub fn event_contracts_for_kind(kind: u32) -> Vec<&'static RadrootsEventContract> { +pub fn event_contracts_for_kind(kind: u32) -> impl Iterator<Item = &'static RadrootsEventContract> { ALL_EVENT_CONTRACTS .iter() - .filter(|contract| contract.kind == kind) - .collect() + .filter(move |contract| contract.kind == kind) } pub fn identify_event_contract( @@ -3540,7 +3602,7 @@ mod tests { #[test] fn event_contract_lookup_supports_many_contracts_per_kind() { - let contracts = event_contracts_for_kind(KIND_LIST_SET_GENERIC); + let contracts = event_contracts_for_kind(KIND_LIST_SET_GENERIC).collect::<Vec<_>>(); assert_eq!(contracts.len(), 6); assert!( contracts @@ -3551,7 +3613,20 @@ mod tests { event_contract("radroots.list_set.member_of.farms.v1").map(|contract| contract.kind), Some(KIND_LIST_SET_GENERIC) ); - assert!(event_contracts_for_kind(999_999).is_empty()); + assert!(event_contracts_for_kind(999_999).next().is_none()); + } + + #[test] + fn event_contract_lookup_supports_knowledge_contract_kinds() { + let contracts = event_contracts_for_kind(KIND_WIKI_ARTICLE).collect::<Vec<_>>(); + assert_eq!(contracts.len(), 1); + assert_eq!(contracts[0].id, "radroots.wiki.article.v1"); + assert_eq!( + identify_event_contract(KIND_WIKI_ARTICLE, &[], "# Soil") + .expect("wiki article contract") + .id, + "radroots.wiki.article.v1" + ); } #[test] @@ -3580,6 +3655,7 @@ mod tests { fn exposes_knowledge_contracts() { let wiki_article = event_contract("radroots.wiki.article.v1").expect("wiki article"); assert_eq!(wiki_article.kind, KIND_WIKI_ARTICLE); + assert_eq!(wiki_article.stability, RadrootsEventStability::Experimental); assert_eq!( kind_contract(KIND_WIKI_ARTICLE) .expect("wiki kind") @@ -3588,8 +3664,23 @@ mod tests { ); assert_eq!(wiki_article.content_schema, RadrootsContentSchema::Djot); + let wiki_merge_request = + event_contract("radroots.wiki.merge_request.v1").expect("wiki merge request"); + assert_eq!( + wiki_merge_request.stability, + RadrootsEventStability::Experimental + ); + assert_eq!( + wiki_merge_request.content_schema, + RadrootsContentSchema::PlainText + ); + let wiki_redirect = event_contract("radroots.wiki.redirect.v1").expect("wiki redirect"); assert_eq!(wiki_redirect.kind, KIND_WIKI_REDIRECT); + assert_eq!( + wiki_redirect.stability, + RadrootsEventStability::Experimental + ); assert_eq!(wiki_redirect.content_schema, RadrootsContentSchema::Empty); for id in [ @@ -3603,6 +3694,7 @@ mod tests { "radroots.knowledge.contribution_attestation.v1", ] { let contract = event_contract(id).expect(id); + assert_eq!(contract.stability, RadrootsEventStability::Experimental); assert_eq!( event_contract_family(contract), Some(RadrootsContractFamily::Knowledge) diff --git a/crates/events_codec/src/manifest.rs b/crates/events_codec/src/manifest.rs @@ -16,7 +16,7 @@ use radroots_events::contract::{ use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -pub const RADROOTS_KNOWLEDGE_CONTRACT_MANIFEST_SCHEMA_VERSION: u32 = 1; +pub const RADROOTS_KNOWLEDGE_CONTRACT_MANIFEST_SCHEMA_VERSION: u32 = 2; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct RadrootsKnowledgeContractManifest { @@ -44,6 +44,9 @@ pub struct RadrootsKnowledgeContractManifestEntry { pub reducers: Vec<String>, pub codec_support: RadrootsKnowledgeManifestCodecSupport, pub sdk_builder_support: bool, + pub sdk_draft_support: bool, + pub wasm_tag_builder_support: bool, + pub wasm_verified_decode_support: bool, pub deprecated: bool, pub replaced_by: Option<String>, pub introduced_at: String, @@ -131,6 +134,7 @@ fn manifest_entry(contract: &RadrootsEventContract) -> RadrootsKnowledgeContract let standard = kind_contract(contract.kind) .map(|contract| standard_label(contract.standard)) .unwrap_or("unknown"); + let mvp_support = mvp_sdk_and_wasm_tag_support(contract.id); RadrootsKnowledgeContractManifestEntry { contract_id: contract.id.to_string(), @@ -158,13 +162,30 @@ fn manifest_entry(contract: &RadrootsEventContract) -> RadrootsKnowledgeContract verified_decode: true, verified_decode_requires_nostr: true, }, - sdk_builder_support: false, + sdk_builder_support: mvp_support, + sdk_draft_support: mvp_support, + wasm_tag_builder_support: mvp_support, + wasm_verified_decode_support: true, deprecated: false, replaced_by: None, introduced_at: env!("CARGO_PKG_VERSION").to_string(), } } +fn mvp_sdk_and_wasm_tag_support(contract_id: &str) -> bool { + matches!( + contract_id, + "radroots.wiki.article.v1" + | "radroots.wiki.redirect.v1" + | "radroots.wiki.merge_request.v1" + | "radroots.knowledge.source.v1" + | "radroots.knowledge.claim.v1" + | "radroots.knowledge.relation.v1" + | "radroots.knowledge.review.v1" + | "radroots.knowledge.field_report.v1" + ) +} + fn discriminator_manifest( discriminator: &RadrootsEventDiscriminator, ) -> RadrootsKnowledgeManifestDiscriminator { diff --git a/crates/events_codec/tests/knowledge_manifest.rs b/crates/events_codec/tests/knowledge_manifest.rs @@ -20,13 +20,30 @@ use radroots_events_codec::manifest::{ use radroots_test_fixtures::knowledge::RADROOTS_KNOWLEDGE_VALID_CONTRACT_IDS; const MANIFEST_JSON: &str = - include_str!("../../../contracts/knowledge/knowledge_event_contract_manifest.v1.json"); + include_str!("../../../contracts/knowledge/knowledge_event_contract_manifest.v2.json"); const MANIFEST_SHA256: &str = - include_str!("../../../contracts/knowledge/knowledge_event_contract_manifest.v1.sha256"); + include_str!("../../../contracts/knowledge/knowledge_event_contract_manifest.v2.sha256"); const REGENPROTO_COMPATIBILITY_VECTOR: &str = include_str!( "../../../contracts/conformance/vectors/knowledge/regenproto_compatibility.v1.json" ); +const MVP_SUPPORT_CONTRACT_IDS: &[&str] = &[ + "radroots.wiki.article.v1", + "radroots.wiki.redirect.v1", + "radroots.wiki.merge_request.v1", + "radroots.knowledge.source.v1", + "radroots.knowledge.claim.v1", + "radroots.knowledge.relation.v1", + "radroots.knowledge.review.v1", + "radroots.knowledge.field_report.v1", +]; + +const BETA_CONTRACT_IDS: &[&str] = &[ + "radroots.knowledge.evidence_bounty.v1", + "radroots.knowledge.change_proposal.v1", + "radroots.knowledge.contribution_attestation.v1", +]; + #[test] fn knowledge_manifest_is_deterministic_and_matches_artifacts() { let first = contract_manifest_json().unwrap(); @@ -72,7 +89,7 @@ fn knowledge_manifest_covers_required_fields_for_every_contract() { assert!(contract.kind > 0); assert!(!contract.class.trim().is_empty()); assert!(!contract.standard.trim().is_empty()); - assert!(!contract.stability.trim().is_empty()); + assert_eq!(contract.stability, "experimental"); assert!(!contract.privacy.trim().is_empty()); assert!(!contract.content_schema.trim().is_empty()); assert!(!contract.payload_type.trim().is_empty()); @@ -84,11 +101,41 @@ fn knowledge_manifest_covers_required_fields_for_every_contract() { assert!(contract.codec_support.encode); assert!(contract.codec_support.decode); assert!(contract.codec_support.contract_validation); + assert!(contract.wasm_verified_decode_support); assert!(!contract.deprecated); assert!(contract.replaced_by.is_none()); assert!(!contract.introduced_at.trim().is_empty()); } + for contract_id in MVP_SUPPORT_CONTRACT_IDS { + let contract = manifest + .contracts + .iter() + .find(|contract| contract.contract_id == *contract_id) + .unwrap(); + assert!(contract.sdk_builder_support, "{contract_id}"); + assert!(contract.sdk_draft_support, "{contract_id}"); + assert!(contract.wasm_tag_builder_support, "{contract_id}"); + } + + for contract_id in BETA_CONTRACT_IDS { + let contract = manifest + .contracts + .iter() + .find(|contract| contract.contract_id == *contract_id) + .unwrap(); + assert!(!contract.sdk_builder_support, "{contract_id}"); + assert!(!contract.sdk_draft_support, "{contract_id}"); + assert!(!contract.wasm_tag_builder_support, "{contract_id}"); + } + + let merge_request = manifest + .contracts + .iter() + .find(|contract| contract.contract_id == "radroots.wiki.merge_request.v1") + .unwrap(); + assert_eq!(merge_request.content_schema, "plain_text"); + let manifest_contracts = manifest .contracts .iter() diff --git a/tools/xtask/src/contract.rs b/tools/xtask/src/contract.rs @@ -13,11 +13,28 @@ const ROOT_RELEASE_POLICY_RELATIVE: &str = const CONFORMANCE_ROOT_RELATIVE: &str = "contracts/conformance"; const CONFORMANCE_SCHEMA_RELATIVE: &str = "contracts/conformance/schema/vector.schema.json"; const KNOWLEDGE_MANIFEST_RELATIVE: &str = - "contracts/knowledge/knowledge_event_contract_manifest.v1.json"; + "contracts/knowledge/knowledge_event_contract_manifest.v2.json"; const KNOWLEDGE_MANIFEST_SHA256_RELATIVE: &str = - "contracts/knowledge/knowledge_event_contract_manifest.v1.sha256"; + "contracts/knowledge/knowledge_event_contract_manifest.v2.sha256"; +const KNOWLEDGE_MANIFEST_AND_DECODE_RELATIVE: &str = + "contracts/conformance/vectors/knowledge/manifest_and_decode.v1.json"; const KNOWLEDGE_REGENPROTO_COMPATIBILITY_RELATIVE: &str = "contracts/conformance/vectors/knowledge/regenproto_compatibility.v1.json"; +const KNOWLEDGE_MVP_SUPPORT_CONTRACT_IDS: [&str; 8] = [ + "radroots.wiki.article.v1", + "radroots.wiki.redirect.v1", + "radroots.wiki.merge_request.v1", + "radroots.knowledge.source.v1", + "radroots.knowledge.claim.v1", + "radroots.knowledge.relation.v1", + "radroots.knowledge.review.v1", + "radroots.knowledge.field_report.v1", +]; +const KNOWLEDGE_BETA_CONTRACT_IDS: [&str; 3] = [ + "radroots.knowledge.evidence_bounty.v1", + "radroots.knowledge.change_proposal.v1", + "radroots.knowledge.contribution_attestation.v1", +]; const RELEASE_POLICY_ENV: &str = "RADROOTS_MOUNTED_RUST_CRATE_PUBLISH_POLICY"; const EVENT_BOUNDARY_MATRIX_ENV: &str = "RADROOTS_EVENT_BOUNDARY_MATRIX"; const COVERAGE_REQUIRED_THRESHOLD: f64 = 100.0; @@ -2099,14 +2116,133 @@ pub fn validate_knowledge_contract_manifest(workspace_root: &Path) -> Result<(), )); } + validate_knowledge_manifest_witnesses(workspace_root, &actual_json)?; + let bundle = load_contract_bundle(workspace_root)?; validate_conformance_vector_file( + &workspace_root.join(KNOWLEDGE_MANIFEST_AND_DECODE_RELATIVE), + &bundle.manifest.contract.version, + )?; + validate_conformance_vector_file( &workspace_root.join(KNOWLEDGE_REGENPROTO_COMPATIBILITY_RELATIVE), &bundle.manifest.contract.version, )?; Ok(()) } +fn validate_knowledge_manifest_witnesses( + workspace_root: &Path, + actual_json: &str, +) -> Result<(), String> { + for relative in legacy_knowledge_manifest_relatives() { + let path = workspace_root.join(&relative); + if path.exists() { + return Err(format!( + "stale knowledge manifest artifact remains at {}", + relative + )); + } + } + + let value = serde_json::from_str::<Value>(actual_json) + .map_err(|error| format!("parse knowledge manifest JSON: {error}"))?; + if value.get("schema_version").and_then(Value::as_u64) != Some(2) { + return Err("knowledge manifest schema_version must be 2".to_string()); + } + let contracts = value + .get("contracts") + .and_then(Value::as_array) + .ok_or_else(|| "knowledge manifest contracts must be an array".to_string())?; + let mut previous_id: Option<String> = None; + let mut ids = BTreeSet::new(); + + for contract in contracts { + let contract_id = contract + .get("contract_id") + .and_then(Value::as_str) + .ok_or_else(|| "knowledge manifest entry missing contract_id".to_string())?; + if let Some(previous) = previous_id.as_deref() { + if previous > contract_id { + return Err( + "knowledge manifest contracts must be sorted by contract_id".to_string() + ); + } + } + previous_id = Some(contract_id.to_string()); + if !ids.insert(contract_id.to_string()) { + return Err(format!( + "knowledge manifest has duplicate contract id {contract_id}" + )); + } + + if contract.get("stability").and_then(Value::as_str) != Some("experimental") { + return Err(format!( + "knowledge manifest contract {contract_id} must be experimental" + )); + } + if contract_id == "radroots.wiki.merge_request.v1" + && contract.get("content_schema").and_then(Value::as_str) != Some("plain_text") + { + return Err( + "wiki merge request manifest content_schema must be plain_text".to_string(), + ); + } + + let sdk_builder_support = manifest_bool_field(contract, "sdk_builder_support")?; + let sdk_draft_support = manifest_bool_field(contract, "sdk_draft_support")?; + let wasm_tag_builder_support = manifest_bool_field(contract, "wasm_tag_builder_support")?; + let wasm_verified_decode_support = + manifest_bool_field(contract, "wasm_verified_decode_support")?; + + if KNOWLEDGE_MVP_SUPPORT_CONTRACT_IDS.contains(&contract_id) { + if !sdk_builder_support || !sdk_draft_support || !wasm_tag_builder_support { + return Err(format!( + "knowledge manifest MVP contract {contract_id} must report SDK and WASM tag support" + )); + } + } + if KNOWLEDGE_BETA_CONTRACT_IDS.contains(&contract_id) + && (sdk_builder_support || sdk_draft_support || wasm_tag_builder_support) + { + return Err(format!( + "knowledge manifest beta contract {contract_id} must not overclaim builder support" + )); + } + if !wasm_verified_decode_support { + return Err(format!( + "knowledge manifest contract {contract_id} must report WASM verified decode support" + )); + } + } + + for contract_id in KNOWLEDGE_MVP_SUPPORT_CONTRACT_IDS + .iter() + .chain(KNOWLEDGE_BETA_CONTRACT_IDS.iter()) + { + if !ids.contains(*contract_id) { + return Err(format!( + "knowledge manifest missing required contract {contract_id}" + )); + } + } + + Ok(()) +} + +fn legacy_knowledge_manifest_relatives() -> [String; 2] { + [ + KNOWLEDGE_MANIFEST_RELATIVE.replace(".v2.", ".v1."), + KNOWLEDGE_MANIFEST_SHA256_RELATIVE.replace(".v2.", ".v1."), + ] +} + +fn manifest_bool_field(contract: &Value, field: &str) -> Result<bool, String> { + contract + .get(field) + .and_then(Value::as_bool) + .ok_or_else(|| format!("knowledge manifest entry missing boolean field {field}")) +} + #[cfg(not(test))] fn validate_missing_conformance_vectors( _workspace_root: &Path,