feat: migrate protocol and HTTP to structured interface references

T-20: identify interfaces by exact scope/name pairs; add optional opaque scope_ref metadata and separate shape validation from path-dependent scope compatibility. Preserve call error precedence, strict JSON, validators and transport boundaries. Mechanically migrate consumers and document API/wire compatibility and deferred lifecycle semantics.

Validation: cargo fmt --all --check; git diff --check; cargo check --workspace --all-targets; cargo test -p wip-protocol -p wip-http; cargo test --workspace (221 passing tests including doctests); cargo test --workspace --all-targets; nix develop --command cargo clippy --workspace --all-targets -- -D warnings; RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps. All pass. cargo/rustc 1.95.0; development-shell Clippy/rustc 1.97.1.
This commit is contained in:
2026-10-07 18:29:51 +09:00
parent bf5fa54f44
commit 8ecc307fc9
34 changed files with 2124 additions and 263 deletions
+20 -14
View File
@@ -16,7 +16,7 @@
//!
//! The canonical API targets operations by `target.path` rather than a mandatory
//! object identifier. Object `ref` and object/interface validators are separate,
//! optional observation fields. Interface references are opaque values observed
//! optional observation fields. Interface references are structured scope/name values observed
//! separately from descriptors, and descriptors contain neither a Rust trait
//! name nor an opaque-reference `name` field. Application records, enums,
//! unions, entry paths, and JSON all encode through the schema-neutral [`Value`]
@@ -46,7 +46,7 @@ use wip_host::{
};
use wip_interface as wip;
use wip_interface::WipType;
use wip_protocol::{ProtocolError, ProtocolErrorCode, Value};
use wip_protocol::{InterfaceReference, ProtocolError, ProtocolErrorCode, Value};
const OBJECT_PATH: &str = "/article-service";
const OBJECT_VALIDATOR: &[u8] = b"object-v1";
@@ -134,12 +134,12 @@ pub struct DemoReport {
pub object_reference: Option<String>,
/// Optional object-state validator.
pub object_validator: Option<Vec<u8>>,
/// Opaque interface references published by the object, in exposure order.
pub published_interfaces: Vec<String>,
/// Opaque reference assigned to the catalog descriptor.
pub catalog_interface_reference: String,
/// Opaque reference assigned to the audit descriptor.
pub audit_interface_reference: String,
/// Structured interface references published by the object, in exposure order.
pub published_interfaces: Vec<InterfaceReference>,
/// Root-scoped reference assigned to the catalog descriptor.
pub catalog_interface_reference: InterfaceReference,
/// Root-scoped reference assigned to the audit descriptor.
pub audit_interface_reference: InterfaceReference,
/// Descriptor format observed separately for the catalog reference.
pub catalog_descriptor_format: String,
/// Descriptor format observed separately for the audit reference.
@@ -296,7 +296,8 @@ pub fn run() -> Result<DemoReport, Box<dyn Error>> {
validator: Some(b"stale-object".to_vec()),
},
interface: InterfaceTarget {
reference: diagnostics.reference().as_str().to_owned(),
scope_ref: None,
reference: diagnostics.reference().protocol_reference(),
validator: Some(b"stale-interface".to_vec()),
},
operation: "missing".into(),
@@ -319,7 +320,10 @@ pub fn run() -> Result<DemoReport, Box<dyn Error>> {
.code;
let interface_not_found = registry
.fetch_interface("missing-interface")
.fetch_interface(&InterfaceReference {
scope: "/".into(),
name: "missing-interface".into(),
})
.expect_err("unknown interface references must not resolve")
.code;
@@ -330,7 +334,8 @@ pub fn run() -> Result<DemoReport, Box<dyn Error>> {
validator: Some(b"stale-object".to_vec()),
},
interface: InterfaceTarget {
reference: diagnostics.reference().as_str().to_owned(),
scope_ref: None,
reference: diagnostics.reference().protocol_reference(),
validator: Some(b"stale-interface".to_vec()),
},
operation: "missing".into(),
@@ -345,8 +350,8 @@ pub fn run() -> Result<DemoReport, Box<dyn Error>> {
object_reference: object.r#ref,
object_validator: object.validator,
published_interfaces: object.interfaces,
catalog_interface_reference: catalog.reference().as_str().to_owned(),
audit_interface_reference: audit.reference().as_str().to_owned(),
catalog_interface_reference: catalog.reference().protocol_reference(),
audit_interface_reference: audit.reference().protocol_reference(),
catalog_descriptor_format: catalog_response.descriptor.format,
audit_descriptor_format: audit_response.descriptor.format,
published,
@@ -412,7 +417,8 @@ fn request(
validator: Some(OBJECT_VALIDATOR.to_vec()),
},
interface: InterfaceTarget {
reference: interface.as_str().to_owned(),
scope_ref: None,
reference: interface.protocol_reference(),
validator: interface_validator.map(<[u8]>::to_vec),
},
operation: operation.into(),
@@ -97,7 +97,7 @@ fn object_fields_keep_path_context_and_optional_ref_separate() {
let object = Object {
name: "article".into(),
description: None,
interfaces: vec!["catalog-ref".into(), "audit-ref".into()],
interfaces: vec![root_reference("catalog-ref"), root_reference("audit-ref")],
r#ref: None,
validator: None,
};
@@ -105,7 +105,10 @@ fn object_fields_keep_path_context_and_optional_ref_separate() {
object.validate_at_path("/library/article").unwrap();
assert_eq!(object.name, "article");
assert_eq!(object.r#ref, None);
assert_eq!(object.interfaces, ["catalog-ref", "audit-ref"]);
assert_eq!(
object.interfaces,
[root_reference("catalog-ref"), root_reference("audit-ref")]
);
}
#[test]
@@ -244,3 +247,10 @@ fn assert_schema_neutral(value: &Value) {
}
}
}
fn root_reference(name: &str) -> wip_protocol::InterfaceReference {
wip_protocol::InterfaceReference {
scope: "/".into(),
name: name.into(),
}
}