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}