Skip to main content

nichlink/registry_core/plugin/contracts/
contracts.rs

1//! Flow contracts and plugin records carried by registration declarations.
2//! 注册声明携带的数据流合同与插件记录。
3//!
4//! These are pure protocol nouns. They live in the plugin module because that
5//! is what they describe; `declaration` re-exports them so a declaration can
6//! keep naming them without a second copy in the tree.
7//! 这些是纯协议名词。它们住在 plugin 模块,因为描述的正是插件;`declaration`
8//! 再导出它们,让声明继续以原名引用,而不在树里出现第二份副本。
9
10#[path = "signing/signing.rs"]
11mod signing;
12
13use std::fmt;
14
15/// A stable host identity. Package names are not enough when several
16/// frameworks share one process.
17/// 稳定的宿主身份。同一进程存在多个框架时,crate 名称并不足以区分目标。
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19pub struct FrameworkId(pub &'static str);
20
21impl FrameworkId {
22    /// Wrap a host identity; the text is used verbatim for equality and display.
23    /// 包装宿主身份;该文本原样用于相等比较与展示。
24    pub const fn new(value: &'static str) -> Self {
25        Self(value)
26    }
27}
28
29impl fmt::Display for FrameworkId {
30    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
31        formatter.write_str(self.0)
32    }
33}
34
35/// A logical replacement slot. Concrete implementations keep their own node identity;
36/// the slot is the stable name that a graft targets.
37/// 逻辑替换插槽。具体实现保留各自 node identity;嫁接针对的是稳定插槽名。
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39pub struct ContractId(pub &'static str);
40
41impl ContractId {
42    /// The undeclared slot id, spelled as the empty string.
43    /// 未声明的插槽 id,即空字符串。
44    pub const NONE: Self = Self("");
45
46    /// Wrap a slot name; an empty string means no slot was declared.
47    /// 包装插槽名;空字符串表示没有声明插槽。
48    pub const fn new(value: &'static str) -> Self {
49        Self(value)
50    }
51}
52
53impl fmt::Display for ContractId {
54    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
55        formatter.write_str(self.0)
56    }
57}
58
59/// The mechanically comparable part of a data-flow contract.
60/// 数据流合同中可以机械比较的部分。
61#[derive(Clone, Copy, Debug, PartialEq, Eq)]
62pub struct FlowContract {
63    /// Slot this contract describes.
64    /// 本合同描述的插槽。
65    pub id: ContractId,
66    /// Contract version; it must match exactly for either comparison to pass.
67    /// 合同版本;两种比较都要求它完全一致。
68    pub version: u32,
69    /// Declared type label of the contract input.
70    /// 合同输入端声明的类型标签。
71    pub input: &'static str,
72    /// Declared type label of the contract output.
73    /// 合同输出端声明的类型标签。
74    pub output: &'static str,
75}
76
77/// Owned flow contract used by file-backed snapshots.
78/// 文件快照使用的拥有型数据流合同。
79///
80/// Compiled declarations keep static strings. Reloaded source owns its
81/// strings, so replacing a file does not leak old contracts.
82/// 编译期声明继续使用静态字符串;热重载源码拥有自己的字符串,替换文件时不会泄漏旧合同。
83#[derive(Clone, Debug, PartialEq, Eq)]
84pub struct OwnedFlowContract {
85    /// Target slot; the owned snapshot's twin of `FlowContract::id`.
86    /// 目标插槽;owned 快照中对应 `FlowContract::id` 的一侧。
87    pub id: String,
88    /// Contract version; the owned twin of `FlowContract::version`.
89    /// 合同版本;`FlowContract::version` 的 owned 孪生。
90    pub version: u32,
91    /// Declared input label; the owned twin of `FlowContract::input`.
92    /// 声明的输入标签;`FlowContract::input` 的 owned 孪生。
93    pub input: String,
94    /// Declared output label; the owned twin of `FlowContract::output`.
95    /// 声明的输出标签;`FlowContract::output` 的 owned 孪生。
96    pub output: String,
97}
98
99impl OwnedFlowContract {
100    /// The undeclared owned contract: empty strings and version zero.
101    /// 未声明的 owned 合同:字符串为空、版本为 0。
102    pub fn none() -> Self {
103        Self {
104            id: String::new(),
105            version: 0,
106            input: String::new(),
107            output: String::new(),
108        }
109    }
110
111    /// Whether this owned contract carries a slot id.
112    /// 本 owned 合同是否带有插槽 id。
113    pub fn is_declared(&self) -> bool {
114        flow_is_declared(&self.id)
115    }
116
117    /// Whether the two owned contracts agree literally or after known domains compare.
118    /// 两份 owned 合同是字面一致,还是在已知语义域比较后相容。
119    pub fn semantically_compatible_with(&self, expected: &Self) -> bool {
120        let left = FlowFields::from_owned(self);
121        let right = FlowFields::from_owned(expected);
122        flow_fields_equal(left, right) || flow_fields_semantically_compatible(left, right)
123    }
124}
125
126impl From<FlowContract> for OwnedFlowContract {
127    fn from(contract: FlowContract) -> Self {
128        Self {
129            id: contract.id.0.to_owned(),
130            version: contract.version,
131            input: contract.input.to_owned(),
132            output: contract.output.to_owned(),
133        }
134    }
135}
136
137impl fmt::Display for OwnedFlowContract {
138    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
139        write!(
140            formatter,
141            "{} v{} ({} -> {})",
142            self.id, self.version, self.input, self.output
143        )
144    }
145}
146
147/// Normalized semantic labels used by tooling when string contracts are too
148/// coarse to explain a mismatch (for example local vs absolute coordinates).
149/// 调试工具使用的规范化语义标签,避免仅凭字符串无法解释坐标域差异。
150#[derive(Clone, Copy, Debug, PartialEq, Eq)]
151pub enum FlowSemantic {
152    /// Label no table entry knows; only literal equality is meaningful.
153    /// 语义表不认识的标签;只有字面相等才有意义。
154    Unknown,
155    /// Coordinates relative to the parent node.
156    /// 相对于父节点的坐标。
157    LocalCoordinates,
158    /// Coordinates in the host's absolute space.
159    /// 宿主绝对空间中的坐标。
160    AbsoluteCoordinates,
161    /// Device-independent pixel units.
162    /// 设备无关的像素单位。
163    LogicalPixels,
164    /// Device pixel units.
165    /// 设备像素单位。
166    PhysicalPixels,
167}
168
169impl FlowContract {
170    /// The undeclared static contract: empty id, version 0, empty labels.
171    /// 未声明的编译期合同:id 为空、版本为 0、标签为空。
172    pub const NONE: Self = Self {
173        id: ContractId::NONE,
174        version: 0,
175        input: "",
176        output: "",
177    };
178
179    /// Build a declared contract from its slot, version, and two type labels.
180    /// 由插槽、版本与两个类型标签构造一份已声明合同。
181    pub const fn new(
182        id: ContractId,
183        version: u32,
184        input: &'static str,
185        output: &'static str,
186    ) -> Self {
187        Self {
188            id,
189            version,
190            input,
191            output,
192        }
193    }
194
195    /// Whether a slot id was declared; the compiled twin of the owned check.
196    /// 是否声明了插槽 id;owned 检查的编译期孪生。
197    pub const fn is_declared(self) -> bool {
198        flow_is_declared(self.id.0)
199    }
200
201    /// Whether the two contracts are literally the same wire contract.
202    /// 两份合同字面上是否是同一个线上合同。
203    ///
204    /// This is the literal half of the comparison pair. Spelling differences in
205    /// known semantic domains are handled by
206    /// [`FlowContract::semantically_compatible_with`], not here, so a caller
207    /// that means "same domain" must call that method.
208    /// 这是比较对中的字面一半。已知语义域里的拼写差异由
209    /// [`FlowContract::semantically_compatible_with`] 处理,不在本方法;因此想要
210    /// "同一语义域"的调用方必须调用那个方法。
211    pub fn compatible_with(self, expected: Self) -> bool {
212        flow_fields_equal(
213            FlowFields::from_declared(self),
214            FlowFields::from_declared(expected),
215        )
216    }
217
218    /// Normalized semantic domain of the declared input label.
219    /// 声明的输入标签所归入的语义域。
220    pub fn input_semantic(self) -> FlowSemantic {
221        flow_semantic(self.input)
222    }
223
224    /// Normalized semantic domain of the declared output label.
225    /// 声明的输出标签所归入的语义域。
226    pub fn output_semantic(self) -> FlowSemantic {
227        flow_semantic(self.output)
228    }
229
230    /// Compare both the wire type and its known semantic domain.
231    /// 同时比较线上的类型名称和已知语义域。
232    pub fn semantically_compatible_with(self, expected: Self) -> bool {
233        let left = FlowFields::from_declared(self);
234        let right = FlowFields::from_declared(expected);
235        flow_fields_equal(left, right) || flow_fields_semantically_compatible(left, right)
236    }
237}
238
239/// The four fields every flow comparison reads, borrowed from either twin.
240/// 每次 flow 比较都会读取的四个字段,从任一孪生借用而来。
241///
242/// `FlowContract` and `OwnedFlowContract` used to compare these fields in two
243/// separate bodies, so the two *pairs* of entry points could drift apart on, say,
244/// whether the version participates. Borrowing the fields once keeps each pair on
245/// one comparison: the literal pair is `FlowContract::compatible_with` and
246/// `OwnedFlowContract`'s `PartialEq`, and the semantic pair is both
247/// `semantically_compatible_with` methods. `compatible_with` deliberately stays
248/// literal-only — it is not the compiled spelling of the semantic entry point —
249/// and `static_and_owned_flow_contracts_compare_identically` pins each pairing
250/// separately so the distinction cannot hide behind the other.
251/// `FlowContract` 与 `OwnedFlowContract` 过去在两个各自的方法体里比较这些字段,因此
252/// 两*对*入口可能在"版本号是否参与比较"这类点上悄悄分叉。只借用一次字段,每一对就共用
253/// 一套比较:字面对是 `FlowContract::compatible_with` 与 `OwnedFlowContract` 的
254/// `PartialEq`,语义对是两个 `semantically_compatible_with`。`compatible_with` 有意
255/// 只做字面比较——它不是语义入口的编译期写法——而
256/// `static_and_owned_flow_contracts_compare_identically` 分别钉住两对,使这一区分无法
257/// 借另一对藏起来。
258#[derive(Clone, Copy)]
259struct FlowFields<'a> {
260    id: &'a str,
261    version: u32,
262    input: &'a str,
263    output: &'a str,
264}
265
266impl<'a> FlowFields<'a> {
267    fn from_declared(contract: FlowContract) -> Self {
268        Self {
269            id: contract.id.0,
270            version: contract.version,
271            input: contract.input,
272            output: contract.output,
273        }
274    }
275
276    fn from_owned(contract: &'a OwnedFlowContract) -> Self {
277        Self {
278            id: &contract.id,
279            version: contract.version,
280            input: &contract.input,
281            output: &contract.output,
282        }
283    }
284}
285
286/// Whether a flow identity was declared at all.
287/// 是否声明了数据流身份。
288///
289/// `FlowContract` compares its `ContractId` newtype while `OwnedFlowContract`
290/// compares an owned `String`, so "is this contract declared?" was spelled
291/// `!self.id.0.is_empty()` in one place and `!self.id.is_empty()` in another.
292/// Both ask the same question about the same text; one const function answers it
293/// and keeps the compiled twin usable in a const context.
294/// `static_and_owned_flow_contracts_compare_identically` pins the two answers.
295/// `FlowContract` 比较的是 `ContractId` newtype,`OwnedFlowContract` 比较的是自有
296/// `String`,因此"该合同是否已声明"一处写成 `!self.id.0.is_empty()`,另一处写成
297/// `!self.id.is_empty()`。两者问的是同一段文本的同一个问题;用一个 const 函数回答,
298/// 编译期孪生也仍可用于 const 环境。
299/// `static_and_owned_flow_contracts_compare_identically` 把两个答案互钉。
300const fn flow_is_declared(id: &str) -> bool {
301    !id.is_empty()
302}
303
304/// Whether two flow field sets are literally the same contract.
305/// 两组 flow 字段是否字面上就是同一份合同。
306fn flow_fields_equal(left: FlowFields<'_>, right: FlowFields<'_>) -> bool {
307    left.id == right.id
308        && left.version == right.version
309        && left.input == right.input
310        && left.output == right.output
311}
312
313/// Whether two flow field sets still agree after known semantic domains compare.
314/// 已知语义域参与比较后,两组 flow 字段是否仍然相容。
315///
316/// The identity and version must match exactly; only the two labels are allowed
317/// to be respelled through [`labels_compatible`], which is the semantic-label
318/// core shared here. The compiled twin reaches this through
319/// `semantically_compatible_with` — *not* through the literal-only
320/// `compatible_with` — while the owned twin used to repeat the whole conjunction
321/// inline, so a fourth term could be added to one and not the other.
322/// 身份与版本必须完全一致;只有两个标签允许经 [`labels_compatible`](此处的语义标签
323/// 核)换一种拼写。编译期孪生经 `semantically_compatible_with`——而**不是**只做字面
324/// 比较的 `compatible_with`——走到这里,而 owned 孪生过去把整个合取式内联重写了一遍,
325/// 因此某一侧多出一个条件时另一侧不会跟着变。
326fn flow_fields_semantically_compatible(left: FlowFields<'_>, right: FlowFields<'_>) -> bool {
327    left.id == right.id
328        && left.version == right.version
329        && labels_compatible(left.input, right.input)
330        && labels_compatible(left.output, right.output)
331}
332
333/// Whether two declared type labels may stand for the same domain.
334/// 两个声明的类型标签是否可能指同一个语义域。
335///
336/// A label the table below does not know carries no domain information, so it
337/// can only agree with itself, literally. Treating two *different* unknown
338/// labels as compatible is exactly what let a type-incompatible replacement
339/// occupy a slot while every check reported success.
340/// 下表不认识的标签不携带任何语义域信息,因此只能与自身字面相等才算一致。把两个
341/// *不同*的未知标签当作兼容,正是类型不兼容的替换件得以占位、而所有检查都报成功的
342/// 原因。
343fn labels_compatible(left: &str, right: &str) -> bool {
344    match (flow_semantic(left), flow_semantic(right)) {
345        (FlowSemantic::Unknown, FlowSemantic::Unknown) => left == right,
346        (FlowSemantic::Unknown, _) | (_, FlowSemantic::Unknown) => false,
347        (left, right) => left == right,
348    }
349}
350
351fn flow_semantic(value: &str) -> FlowSemantic {
352    if value == "LocalCoordinates" || value == "local_coordinates" {
353        FlowSemantic::LocalCoordinates
354    } else if value == "AbsoluteCoordinates" || value == "absolute_coordinates" {
355        FlowSemantic::AbsoluteCoordinates
356    } else if value == "LogicalPixels" || value == "logical_pixels" {
357        FlowSemantic::LogicalPixels
358    } else if value == "PhysicalPixels" || value == "physical_pixels" {
359        FlowSemantic::PhysicalPixels
360    } else {
361        FlowSemantic::Unknown
362    }
363}
364
365/// A handle can expose its data-flow contract once; registration faces then
366/// read it without repeating input/output strings.
367/// handle 只需声明一次数据流合同;注册面直接读取,不重复填写输入输出字符串。
368pub trait FlowContractProvider {
369    /// The one contract this face declares, exposed as a compile-time constant.
370    /// 本面声明的那一份合同,以编译期常量形式暴露。
371    const FLOW_CONTRACT: FlowContract;
372}
373
374/// Whether a plugin adds a new capability or replaces an existing slot.
375/// 插件是增加能力还是替换已有插槽。
376#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
377pub enum PluginMode {
378    /// Adds a new face without removing any existing one.
379    /// 增加新面,不移除任何已有面。
380    Extension,
381    /// Takes over the slot occupied by an existing face.
382    /// 接管已有面所占的插槽。
383    Replacement,
384}
385
386/// Where the plugin came from. Trust policy is deliberately separate from
387/// registration structure and flow compatibility.
388/// 插件来源。信任策略与注册结构、数据流兼容性刻意分离。
389#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
390pub enum PluginSource {
391    /// Published and signed by the official distribution.
392    /// 由官方分发渠道发布并签名。
393    Official,
394    /// Supplied by a user or a third party.
395    /// 由用户或第三方提供。
396    User,
397}
398
399impl PluginSource {
400    #[doc(hidden)]
401    pub fn parse(value: &str) -> Option<Self> {
402        match value {
403            "official" => Some(Self::Official),
404            "user" => Some(Self::User),
405            _ => None,
406        }
407    }
408}
409
410impl PluginMode {
411    #[doc(hidden)]
412    pub fn parse(value: &str) -> Option<Self> {
413        match value {
414            "extension" => Some(Self::Extension),
415            "replacement" => Some(Self::Replacement),
416            _ => None,
417        }
418    }
419}
420
421/// Plugin manifest metadata.
422/// 插件 manifest 元数据。
423#[derive(Clone, Copy, Debug, PartialEq, Eq)]
424pub struct PluginManifest {
425    /// Manifest name; trust and lock checks match on this package identity.
426    /// manifest 名称;信任与锁检查以它作为包身份匹配。
427    pub name: &'static str,
428    /// Rust crate that carries the plugin implementation.
429    /// 承载插件实现的 Rust crate。
430    pub crate_name: &'static str,
431    /// Declared plugin version.
432    /// 声明的插件版本。
433    pub version: &'static str,
434    /// Host framework the plugin targets.
435    /// 插件针对的宿主框架。
436    pub framework: FrameworkId,
437    /// Trust lane the plugin came from.
438    /// 插件的来源信任通道。
439    pub source: PluginSource,
440    /// Whether the plugin extends or replaces a slot.
441    /// 插件是扩展还是替换插槽。
442    pub mode: PluginMode,
443    /// Digest of the plugin bytes, optionally carrying a `sha256:` prefix.
444    /// 插件字节的摘要,可带 `sha256:` 前缀。
445    pub checksum: &'static str,
446    /// Hex-encoded Ed25519 signature over `signing_payload`, when supplied.
447    /// `signing_payload` 上的十六进制 Ed25519 签名;插件没有签名时为 None。
448    pub signature: Option<&'static str>,
449    /// Fingerprint of the public key used for the signature.
450    /// 用于签名的公钥指纹。
451    pub public_key_fingerprint: Option<&'static str>,
452    /// Identifier of the revocation-list snapshot used by the publisher.
453    /// 发布者使用的撤销列表快照标识。
454    pub revocation_list: Option<&'static str>,
455}
456
457impl PluginManifest {
458    /// Whether the manifest names exactly this framework.
459    /// manifest 指明的框架是否就是这一个。
460    pub fn targets(self, framework: FrameworkId) -> bool {
461        self.framework.0 == framework.0
462    }
463
464    /// Verify the manifest digest against plugin bytes before native loading.
465    /// 在 native 加载前,用插件字节验证 manifest 摘要。
466    pub fn verify_bytes(self, bytes: &[u8]) -> bool {
467        let expected = self
468            .checksum
469            .strip_prefix("sha256:")
470            .unwrap_or(self.checksum);
471        expected.len() == 64 && crate::sha256_hex(bytes).eq_ignore_ascii_case(expected)
472    }
473
474    /// Build the canonical bytes covered by an official plugin signature.
475    /// 构造官方插件签名覆盖的规范化字节。
476    ///
477    /// The payload covers the manifest's own fields, the plugin bytes, **and**
478    /// every field of the `registration` those bytes are claimed to produce. A
479    /// signature over only the first two left the registration unauthenticated:
480    /// a tampered `parent` or `flow` still verified as `Signature` and could
481    /// pass a slot contract check the honest artifact failed. The manifest's
482    /// `signature` field is excluded, because a signature cannot cover the bytes
483    /// that carry it.
484    /// 载荷覆盖 manifest 自身字段、插件字节,**以及**这些字节声称产出的 `registration` 的每个
485    /// 字段。只覆盖前两者的签名让注册声明完全未被认证:被篡改的 `parent` 或 `flow` 仍会验证为
486    /// `Signature`,甚至能通过诚实工件通不过的槽位合同检查。manifest 的 `signature` 字段被排除,
487    /// 因为签名无法覆盖承载它的那段字节。
488    pub fn signing_payload(self, registration: &crate::RegistrationInfo, bytes: &[u8]) -> Vec<u8> {
489        let mut payload = Vec::with_capacity(bytes.len() + 512);
490        signing::manifest(&mut payload, self);
491        signing::registration(registration, &mut payload);
492        payload.extend_from_slice(&(bytes.len() as u64).to_le_bytes());
493        payload.extend_from_slice(bytes);
494        payload
495    }
496}
497
498#[cfg(test)]
499mod flow_label_tests {
500    use super::{ContractId, FlowContract, OwnedFlowContract};
501
502    /// An output label the table does not know must not be smuggled past the
503    /// gate by comparing two unknowns with each other.
504    /// 语义表不认识的输出标签,不得靠"两个未知标签互相比较"混过关口。
505    #[test]
506    fn unknown_output_labels_must_agree_literally() {
507        let target = FlowContract::new(
508            ContractId::new("render.v1"),
509            1,
510            "LocalCoordinates",
511            "CanvasFrame",
512        );
513        let different = FlowContract::new(
514            ContractId::new("render.v1"),
515            1,
516            "LocalCoordinates",
517            "TotallyDifferentType",
518        );
519        assert!(!target.semantically_compatible_with(different));
520        assert!(target.semantically_compatible_with(target));
521    }
522
523    /// Spelling differences are still authorised where the table knows the
524    /// domain, as long as the unknown side agrees literally.
525    /// 语义表认识的域仍允许拼写差异,只要未知的那一侧字面一致。
526    #[test]
527    fn known_domains_keep_accepting_spelling_differences() {
528        let target = FlowContract::new(
529            ContractId::new("render.v1"),
530            1,
531            "LocalCoordinates",
532            "CanvasFrame",
533        );
534        let respelled = FlowContract::new(
535            ContractId::new("render.v1"),
536            1,
537            "local_coordinates",
538            "CanvasFrame",
539        );
540        assert!(target.semantically_compatible_with(respelled));
541        let respelled_with_other_output = FlowContract::new(
542            ContractId::new("render.v1"),
543            1,
544            "local_coordinates",
545            "TotallyDifferentType",
546        );
547        assert!(!target.semantically_compatible_with(respelled_with_other_output));
548    }
549
550    /// The compiled and owned twins share one field comparison and one
551    /// semantic-label core, so each pair must get the same answer across the
552    /// matrix: the literal pair (`compatible_with` ↔ owned `==`) and the semantic
553    /// pair (both `semantically_compatible_with`).
554    /// 编译期与 owned 孪生共用一套字段比较与一个语义标签核,因此每一对在矩阵上都必须
555    /// 得到相同答案:字面对(`compatible_with` ↔ owned `==`)与语义对(两个
556    /// `semantically_compatible_with`)。
557    #[test]
558    fn static_and_owned_flow_contracts_compare_identically() {
559        let cases = [
560            ("render.v1", 1, "LocalCoordinates", "CanvasFrame"),
561            ("render.v1", 1, "local_coordinates", "CanvasFrame"),
562            ("render.v1", 1, "LocalCoordinates", "TotallyDifferentType"),
563            ("render.v1", 2, "LocalCoordinates", "CanvasFrame"),
564            ("render.v2", 1, "LocalCoordinates", "CanvasFrame"),
565            ("", 0, "", ""),
566        ];
567        for &(id, version, input, output) in &cases {
568            for &(other_id, other_version, other_input, other_output) in &cases {
569                let left = FlowContract::new(ContractId::new(id), version, input, output);
570                let right = FlowContract::new(
571                    ContractId::new(other_id),
572                    other_version,
573                    other_input,
574                    other_output,
575                );
576                let owned_left = OwnedFlowContract::from(left);
577                let owned_right = OwnedFlowContract::from(right);
578                assert_eq!(
579                    left.is_declared(),
580                    owned_left.is_declared(),
581                    "is_declared disagrees for {left:?}"
582                );
583                assert_eq!(
584                    left.compatible_with(right),
585                    owned_left == owned_right,
586                    "literal comparison disagrees for {left:?} vs {right:?}"
587                );
588                assert_eq!(
589                    left.semantically_compatible_with(right),
590                    owned_left.semantically_compatible_with(&owned_right),
591                    "semantic comparison disagrees for {left:?} vs {right:?}"
592                );
593            }
594        }
595    }
596
597    /// F3 pin: `compatible_with` is literal-only, and the semantic entry point
598    /// asks a different question. `LocalCoordinates` versus `local_coordinates`
599    /// is the counterexample — the literal pair rejects the pair, the semantic
600    /// pair accepts it. The matrix above cannot expose this divergence because
601    /// it pairs each entry point with its true counterpart, so this test states
602    /// the divergence and both pairings explicitly.
603    /// F3 钉:`compatible_with` 只做字面比较,语义入口问的是另一个问题。
604    /// `LocalCoordinates` 与 `local_coordinates` 就是反例——字面对拒绝这一对,语义对
605    /// 接受。上面的矩阵无法暴露这处分歧,因为它把每个入口与它真正的对应物配对,因此本
606    /// 测试把这处分歧与两种配对都显式写出。
607    #[test]
608    fn compatible_with_stays_literal_while_the_semantic_entry_point_folds_spellings() {
609        let literal = FlowContract::new(
610            ContractId::new("render.v1"),
611            1,
612            "LocalCoordinates",
613            "CanvasFrame",
614        );
615        let respelled = FlowContract::new(
616            ContractId::new("render.v1"),
617            1,
618            "local_coordinates",
619            "CanvasFrame",
620        );
621        assert!(
622            !literal.compatible_with(respelled),
623            "compatible_with must stay a literal comparison"
624        );
625        assert!(
626            literal.semantically_compatible_with(respelled),
627            "the semantic entry point folds known-domain spellings"
628        );
629        assert_eq!(
630            literal.compatible_with(respelled),
631            OwnedFlowContract::from(literal) == OwnedFlowContract::from(respelled)
632        );
633        assert_eq!(
634            literal.semantically_compatible_with(respelled),
635            OwnedFlowContract::from(literal)
636                .semantically_compatible_with(&OwnedFlowContract::from(respelled))
637        );
638    }
639}