lib

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

commit aa7c928953027d9f97e49f2ecc2e189cdae578e4
parent 6fd68043a2df78aa16cb7d9026f7e45cd8851b72
Author: triesap <tyson@radroots.org>
Date:   Thu,  9 Jul 2026 17:49:48 +0000

mesh: align agent status schema contract

- replace mesh-agent status rows with canonical transport status fields
- remove retired readiness and redacted-message schema surfaces
- reject retired status fields and variants in schema validation
- verify mesh-agent proto check, tests, and contract validation

Diffstat:
Mcrates/mesh_agent_proto/schema/radroots_mesh_agent_v1.capnp | 30++++++++++--------------------
Mcrates/mesh_agent_proto/src/schema_validation.rs | 157+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mcrates/mesh_agent_proto/tests/schema.rs | 138+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
3 files changed, 251 insertions(+), 74 deletions(-)

diff --git a/crates/mesh_agent_proto/schema/radroots_mesh_agent_v1.capnp b/crates/mesh_agent_proto/schema/radroots_mesh_agent_v1.capnp @@ -45,33 +45,23 @@ struct MeshAgentStatusRequest { } struct MeshAgentStatusResponse { - readiness @0 :MeshAgentReadinessState; - implementationState @1 :MeshAgentImplementationState; - transports @2 :List(MeshAgentTransportStatus); + transports @0 :List(MeshAgentTransportStatus); } struct MeshAgentTransportStatus { - transportKind @0 :MeshAgentTransportKind; + transport @0 :MeshAgentTransportKind; profileId @1 :Text; endpointUri @2 :Text; - readiness @3 :MeshAgentReadinessState; - implementationState @4 :MeshAgentImplementationState; - publishUsable @5 :Bool; - fetchUsable @6 :Bool; - redactedMessage @7 :Text; -} - -enum MeshAgentReadinessState { - ready @0; - disabled @1; - misconfigured @2; - previewUnavailable @3; + configured @3 :Bool; + implementation @4 :MeshAgentImplementation; + usableForDelivery @5 :Bool; + message @6 :Text; } -enum MeshAgentImplementationState { - previewNoop @0; +enum MeshAgentImplementation { + real @0; mock @1; - real @2; + previewUnavailable @2; } enum MeshAgentTransportKind { @@ -96,7 +86,7 @@ struct MeshAgentTransportReceipt { transportKind @0 :MeshAgentTransportKind; endpointUri @1 :Text; outcome @2 :MeshAgentTransportOutcome; - redactedMessage @3 :Text; + message @3 :Text; } enum MeshAgentTransportOutcome { diff --git a/crates/mesh_agent_proto/src/schema_validation.rs b/crates/mesh_agent_proto/src/schema_validation.rs @@ -20,6 +20,16 @@ struct RequiredVariant { ordinal: u16, } +struct ForbiddenField { + owner: &'static str, + name: &'static str, +} + +struct ForbiddenVariant { + owner: &'static str, + name: &'static str, +} + const REQUEST_FIELDS: &[RequiredField] = &[ field("MeshAgentRequest", "requestId", 0, "Text"), field("MeshAgentRequest", "action", 1, "MeshAgentAction"), @@ -80,63 +90,55 @@ const STATUS_FIELDS: &[RequiredField] = &[ field("MeshAgentStatusRequest", "includeTransports", 0, "Bool"), field( "MeshAgentStatusResponse", - "readiness", - 0, - "MeshAgentReadinessState", - ), - field( - "MeshAgentStatusResponse", - "implementationState", - 1, - "MeshAgentImplementationState", - ), - field( - "MeshAgentStatusResponse", "transports", - 2, + 0, "List(MeshAgentTransportStatus)", ), field( "MeshAgentTransportStatus", - "transportKind", + "transport", 0, "MeshAgentTransportKind", ), field("MeshAgentTransportStatus", "profileId", 1, "Text"), field("MeshAgentTransportStatus", "endpointUri", 2, "Text"), + field("MeshAgentTransportStatus", "configured", 3, "Bool"), field( "MeshAgentTransportStatus", - "readiness", - 3, - "MeshAgentReadinessState", - ), - field( - "MeshAgentTransportStatus", - "implementationState", + "implementation", 4, - "MeshAgentImplementationState", + "MeshAgentImplementation", ), - field("MeshAgentTransportStatus", "publishUsable", 5, "Bool"), - field("MeshAgentTransportStatus", "fetchUsable", 6, "Bool"), - field("MeshAgentTransportStatus", "redactedMessage", 7, "Text"), -]; - -const READINESS_VARIANTS: &[RequiredVariant] = &[ - variant("MeshAgentReadinessState", "ready", 0), - variant("MeshAgentReadinessState", "disabled", 1), - variant("MeshAgentReadinessState", "misconfigured", 2), - variant("MeshAgentReadinessState", "previewUnavailable", 3), + field("MeshAgentTransportStatus", "usableForDelivery", 5, "Bool"), + field("MeshAgentTransportStatus", "message", 6, "Text"), ]; const IMPLEMENTATION_VARIANTS: &[RequiredVariant] = &[ - variant("MeshAgentImplementationState", "previewNoop", 0), - variant("MeshAgentImplementationState", "mock", 1), - variant("MeshAgentImplementationState", "real", 2), + variant("MeshAgentImplementation", "real", 0), + variant("MeshAgentImplementation", "mock", 1), + variant("MeshAgentImplementation", "previewUnavailable", 2), ]; const TRANSPORT_KIND_VARIANTS: &[RequiredVariant] = &[variant("MeshAgentTransportKind", "reticulum", 0)]; +const FORBIDDEN_STATUS_FIELDS: &[ForbiddenField] = &[ + forbidden_field("MeshAgentStatusResponse", "readiness"), + forbidden_field("MeshAgentStatusResponse", "implementationState"), + forbidden_field("MeshAgentTransportStatus", "transportKind"), + forbidden_field("MeshAgentTransportStatus", "readiness"), + forbidden_field("MeshAgentTransportStatus", "implementationState"), + forbidden_field("MeshAgentTransportStatus", "publishUsable"), + forbidden_field("MeshAgentTransportStatus", "fetchUsable"), + forbidden_field("MeshAgentTransportStatus", "redactedMessage"), +]; + +const FORBIDDEN_STATUS_VARIANTS: &[ForbiddenVariant] = + &[forbidden_variant("MeshAgentImplementation", "previewNoop")]; + +const FORBIDDEN_STATUS_ENUMS: &[&str] = + &["MeshAgentReadinessState", "MeshAgentImplementationState"]; + const TRANSPORT_OUTCOME_VARIANTS: &[RequiredVariant] = &[ variant("MeshAgentTransportOutcome", "accepted", 0), variant("MeshAgentTransportOutcome", "delivered", 1), @@ -181,9 +183,14 @@ const PUBLISH_FIELDS: &[RequiredField] = &[ 2, "MeshAgentTransportOutcome", ), - field("MeshAgentTransportReceipt", "redactedMessage", 3, "Text"), + field("MeshAgentTransportReceipt", "message", 3, "Text"), ]; +const FORBIDDEN_PUBLISH_FIELDS: &[ForbiddenField] = &[forbidden_field( + "MeshAgentTransportReceipt", + "redactedMessage", +)]; + const ERROR_FIELDS: &[RequiredField] = &[ field("MeshAgentError", "code", 0, "Text"), field("MeshAgentError", "message", 1, "Text"), @@ -211,6 +218,14 @@ const fn variant(owner: &'static str, name: &'static str, ordinal: u16) -> Requi } } +const fn forbidden_field(owner: &'static str, name: &'static str) -> ForbiddenField { + ForbiddenField { owner, name } +} + +const fn forbidden_variant(owner: &'static str, name: &'static str) -> ForbiddenVariant { + ForbiddenVariant { owner, name } +} + pub(crate) fn validate_schema_text(schema: &str) -> Result<(), RadrootsMeshAgentProtoError> { let parsed = parse_schema(schema)?; if parsed.schema_id.as_deref() != Some(RADROOTS_MESH_AGENT_SCHEMA_ID) { @@ -251,17 +266,27 @@ pub(crate) fn validate_schema_text(schema: &str) -> Result<(), RadrootsMeshAgent )?; validate_variants( &parsed, - READINESS_VARIANTS, + IMPLEMENTATION_VARIANTS, RadrootsMeshAgentProtoError::MissingStatusSurface, )?; validate_variants( &parsed, - IMPLEMENTATION_VARIANTS, + TRANSPORT_KIND_VARIANTS, RadrootsMeshAgentProtoError::MissingStatusSurface, )?; - validate_variants( + reject_fields( &parsed, - TRANSPORT_KIND_VARIANTS, + FORBIDDEN_STATUS_FIELDS, + RadrootsMeshAgentProtoError::MissingStatusSurface, + )?; + reject_variants( + &parsed, + FORBIDDEN_STATUS_VARIANTS, + RadrootsMeshAgentProtoError::MissingStatusSurface, + )?; + reject_enums( + &parsed, + FORBIDDEN_STATUS_ENUMS, RadrootsMeshAgentProtoError::MissingStatusSurface, )?; validate_fields( @@ -274,6 +299,11 @@ pub(crate) fn validate_schema_text(schema: &str) -> Result<(), RadrootsMeshAgent TRANSPORT_OUTCOME_VARIANTS, RadrootsMeshAgentProtoError::MissingPublishSurface, )?; + reject_fields( + &parsed, + FORBIDDEN_PUBLISH_FIELDS, + RadrootsMeshAgentProtoError::MissingPublishSurface, + )?; validate_fields( &parsed, ERROR_FIELDS, @@ -702,3 +732,50 @@ fn validate_variants( } Ok(()) } + +fn reject_fields( + ast: &SchemaAst, + fields: &[ForbiddenField], + error: RadrootsMeshAgentProtoError, +) -> Result<(), RadrootsMeshAgentProtoError> { + for forbidden in fields { + if ast + .structs + .get(forbidden.owner) + .is_some_and(|decl| decl.fields.contains_key(forbidden.name)) + { + return Err(error.clone()); + } + } + Ok(()) +} + +fn reject_variants( + ast: &SchemaAst, + variants: &[ForbiddenVariant], + error: RadrootsMeshAgentProtoError, +) -> Result<(), RadrootsMeshAgentProtoError> { + for forbidden in variants { + if ast + .enums + .get(forbidden.owner) + .is_some_and(|decl| decl.variants.contains_key(forbidden.name)) + { + return Err(error.clone()); + } + } + Ok(()) +} + +fn reject_enums( + ast: &SchemaAst, + enums: &[&str], + error: RadrootsMeshAgentProtoError, +) -> Result<(), RadrootsMeshAgentProtoError> { + for forbidden in enums { + if ast.enums.contains_key(*forbidden) { + return Err(error.clone()); + } + } + Ok(()) +} diff --git a/crates/mesh_agent_proto/tests/schema.rs b/crates/mesh_agent_proto/tests/schema.rs @@ -45,15 +45,16 @@ fn schema_covers_transport_v1_status_and_publish_intent() { "statusResponse @4 :MeshAgentStatusResponse;", "publishResponse @5 :MeshAgentPublishResponse;", "includeTransports @0 :Bool;", - "readiness @0 :MeshAgentReadinessState;", - "implementationState @1 :MeshAgentImplementationState;", - "transports @2 :List(MeshAgentTransportStatus);", - "transportKind @0 :MeshAgentTransportKind;", + "transports @0 :List(MeshAgentTransportStatus);", + "transport @0 :MeshAgentTransportKind;", "profileId @1 :Text;", - "publishUsable @5 :Bool;", - "fetchUsable @6 :Bool;", - "previewUnavailable @3;", - "previewNoop @0;", + "configured @3 :Bool;", + "implementation @4 :MeshAgentImplementation;", + "usableForDelivery @5 :Bool;", + "message @6 :Text;", + "real @0;", + "mock @1;", + "previewUnavailable @2;", "reticulum @0;", "publishRequestId @0 :Text;", "payloadCbor @1 :Data;", @@ -71,6 +72,25 @@ fn schema_covers_transport_v1_status_and_publish_intent() { } #[test] +fn schema_excludes_retired_transport_status_vocabulary() { + validate_schema().expect("schema validates"); + + for forbidden in [ + "readiness", + "implementationState", + "publishUsable", + "fetchUsable", + "redactedMessage", + "previewNoop", + ] { + assert!( + !RADROOTS_MESH_AGENT_SCHEMA.contains(forbidden), + "{forbidden}" + ); + } +} + +#[test] fn schema_hash_is_deterministic() { let first = schema_sha256_hex(); let second = schema_sha256_hex(); @@ -78,7 +98,7 @@ fn schema_hash_is_deterministic() { assert_eq!(first, second); assert_eq!( first, - "eff807dfb46416296b51fa0214fee869fbe7ec1da068ffc9c1ce4facc308be17" + "712aaa11dfec25abf44edb3b0be447f0596442271d46a8b1d9fedb7c3df00bb2" ); } @@ -169,15 +189,15 @@ fn schema_validator_reports_each_missing_required_surface() { ), ( valid.replace( - "enum MeshAgentReadinessState", - "enum MissingMeshAgentReadinessState", + "transports @0 :List(MeshAgentTransportStatus);", + "transportStatuses @0 :List(MeshAgentTransportStatus);", ), RadrootsMeshAgentProtoError::MissingStatusSurface, ), ( valid.replace( - "implementationState @1 :MeshAgentImplementationState;", - "implementation @1 :MeshAgentImplementationState;", + "transport @0 :MeshAgentTransportKind;", + "transportKind @0 :MeshAgentTransportKind;", ), RadrootsMeshAgentProtoError::MissingStatusSurface, ), @@ -186,11 +206,30 @@ fn schema_validator_reports_each_missing_required_surface() { RadrootsMeshAgentProtoError::MissingStatusSurface, ), ( + valid.replace("configured @3 :Bool;", "configured @3 :Text;"), + RadrootsMeshAgentProtoError::MissingStatusSurface, + ), + ( + valid.replace( + "implementation @4 :MeshAgentImplementation;", + "implementation @4 :Text;", + ), + RadrootsMeshAgentProtoError::MissingStatusSurface, + ), + ( + valid.replace("usableForDelivery @5 :Bool;", "publishUsable @5 :Bool;"), + RadrootsMeshAgentProtoError::MissingStatusSurface, + ), + ( + valid.replace("message @6 :Text;", "redactedMessage @6 :Text;"), + RadrootsMeshAgentProtoError::MissingStatusSurface, + ), + ( valid.replace("reticulum @0;", "reticulumPreview @0;"), RadrootsMeshAgentProtoError::MissingStatusSurface, ), ( - valid.replace("previewNoop @0;", "previewUnavailable @0;"), + valid.replace("previewUnavailable @2;", "previewUnavailable @3;"), RadrootsMeshAgentProtoError::MissingStatusSurface, ), ( @@ -227,6 +266,10 @@ fn schema_validator_reports_each_missing_required_surface() { RadrootsMeshAgentProtoError::MissingPublishSurface, ), ( + valid.replace("message @3 :Text;", "redactedMessage @3 :Text;"), + RadrootsMeshAgentProtoError::MissingPublishSurface, + ), + ( valid.replace( "transportUnavailable @8;", "transportUnavailablePreview @8;", @@ -245,6 +288,73 @@ fn schema_validator_reports_each_missing_required_surface() { } #[test] +fn schema_validator_rejects_reintroduced_retired_status_vocabulary() { + let valid = RADROOTS_MESH_AGENT_SCHEMA; + let cases = [ + ( + valid.replace( + " transports @0 :List(MeshAgentTransportStatus);", + " transports @0 :List(MeshAgentTransportStatus);\n readiness @1 :MeshAgentReadinessState;", + ), + RadrootsMeshAgentProtoError::MissingStatusSurface, + ), + ( + valid.replace( + " transports @0 :List(MeshAgentTransportStatus);", + " transports @0 :List(MeshAgentTransportStatus);\n implementationState @1 :MeshAgentImplementationState;", + ), + RadrootsMeshAgentProtoError::MissingStatusSurface, + ), + ( + valid.replace( + " message @6 :Text;", + " message @6 :Text;\n publishUsable @7 :Bool;", + ), + RadrootsMeshAgentProtoError::MissingStatusSurface, + ), + ( + valid.replace( + " message @6 :Text;", + " message @6 :Text;\n fetchUsable @7 :Bool;", + ), + RadrootsMeshAgentProtoError::MissingStatusSurface, + ), + ( + valid.replace( + " message @6 :Text;", + " message @6 :Text;\n redactedMessage @7 :Text;", + ), + RadrootsMeshAgentProtoError::MissingStatusSurface, + ), + ( + valid.replace( + " previewUnavailable @2;", + " previewUnavailable @2;\n previewNoop @3;", + ), + RadrootsMeshAgentProtoError::MissingStatusSurface, + ), + ( + valid.replace( + "enum MeshAgentTransportKind", + "enum MeshAgentReadinessState {\n ready @0;\n}\n\nenum MeshAgentTransportKind", + ), + RadrootsMeshAgentProtoError::MissingStatusSurface, + ), + ( + valid.replace( + " message @3 :Text;", + " message @3 :Text;\n redactedMessage @4 :Text;", + ), + RadrootsMeshAgentProtoError::MissingPublishSurface, + ), + ]; + + for (schema, error) in cases { + assert_eq!(validate_schema_text(schema.as_str()), Err(error)); + } +} + +#[test] fn schema_validator_rejects_commented_required_declarations() { let schema = RADROOTS_MESH_AGENT_SCHEMA.replace(" frameCbor @2 :Data;", " # frameCbor @2 :Data;");