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:
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;");