diff --git a/.spec/decisions/0006-capability-keys-have-no-raw-constructor.md b/.spec/decisions/0006-capability-keys-have-no-raw-constructor.md new file mode 100644 index 0000000..8c3cc40 --- /dev/null +++ b/.spec/decisions/0006-capability-keys-have-no-raw-constructor.md @@ -0,0 +1,36 @@ +# 0006 · Capability key 只由生成注册表投影,不保留裸构造器 + +- 日期:2026-08-29 +- 状态:生效 + +## 背景 + +D-015 裁决(架构源 ADR-040 §7.1 / ADR-048)把 capability **键空间**定为 +`ids/index.json` 独有,架构生成器是唯一发射方,下游只消费投影;裁决前作为 +「唯一正确模型」的仓内私有键值表就此变成违规。 + +本仓 `CapabilityKey` 当时是 `from_local_index(u32)` 公共构造器 + 本地序号, +正是被取代的那种模型:任何调用方都能凭空造一个注册表里没有的键,而且不会有 +任何东西报错。 + +## 决策 + +`CapabilityKey` **不保留任何裸构造器**——crate 内外都没有。唯一构造路径是 +`from_registered(ArchitectureCapabilityKey)`(及按 id 查表的 +`from_registry_id`),而 `ArchitectureCapabilityKey` 只在 +`cargo xtask gen-contracts` 生成的表里存在。 + +`gen-contracts` 同时做三方交叉核对:`ids/index.json` 的 `Capability` 命名空间、 +镜像 C Header 的 `LUMIO_CAPABILITY_` 常量、以及 +`LUMIO_CAPABILITY_COUNT` 必须逐值一致,任一不一致直接生成失败。 +`LUMIO_CAPABILITY_BITS` 显式排除在键空间外:D-015 只裁了键,掩码还是计数、 +以及任何 bit 位指派仍未冻结,`registry::capability_bits()` 保持为空。 + +## 后果 + +- 仓内无法表达一个上游没注册的 capability;真有需要必须先在架构源注册,接受 + 这条往返延迟。 +- 测试只能用已发布的键(如 `Native` / `HybridCLR`),不能再用任意序号构造 + 用例,可读性反而变好,但重命名上游 id 会同时改到测试。 +- 镜像半新半旧(改了 Header 忘了重跑生成器,或反之)在生成期即失败,不会拖到 + 运行期才发现。 diff --git a/.spec/decisions/README.md b/.spec/decisions/README.md index 537a97d..8eabfee 100644 --- a/.spec/decisions/README.md +++ b/.spec/decisions/README.md @@ -33,3 +33,4 @@ | [0003](0003-ffi-buffer-classes-and-leases.md) | FFI Buffer 三分类 + 异步租约,按 provenance 定释放方 | 生效 | | [0004](0004-job-state-machine-and-clock-port.md) | Job 状态机 CAS 线性化 + 私有单调时钟 port | 生效 | | [0005](0005-codec-diagnostics-pending-and-dual-status.md) | codec/diagnostics 维持 pending,模块引入双状态标注 | 生效 | +| [0006](0006-capability-keys-have-no-raw-constructor.md) | Capability key 只由生成注册表投影,不保留裸构造器 | 生效 | diff --git a/.spec/knowledge/standards/repository-architecture.md b/.spec/knowledge/standards/repository-architecture.md index 51d211d..0edf28e 100644 --- a/.spec/knowledge/standards/repository-architecture.md +++ b/.spec/knowledge/standards/repository-architecture.md @@ -28,5 +28,5 @@ metadata: - 逐 Entity/逐 Voxel/逐包 FFI 必须先证明批处理不足;不得把跨边界调用开销扩散到上层。 - 结果必须可诊断、可取消、可重复;不能以“调用方自行保证”替代契约,panic 必须在 ABI 边界转换为稳定错误。 - ABI/Capability/Error Schema、ID 与 Fixture 只在架构源维护;本仓消费已发布 Baseline,不复制生成器或第二套 Schema。 -- Root ABI 消费机制(ADR-040 §7):上游发布物字节级镜像在 [`docs/architecture/abi/`](../../../docs/architecture/abi/README.md)(钉 revision + `.baseline.sha256` 钉 Hash),Rust 侧数值经 `cargo xtask gen-contracts` 从镜像生成,测试与镜像互证;ErrorCode 数值权威只有 `ids/index.json`,Capability bit(D-015)与非 `linux-x86_64-glibc` 布局(D-016)保持不绑定。 +- Root ABI 消费机制(ADR-040 §7):上游发布物字节级镜像在 [`docs/architecture/abi/`](../../../docs/architecture/abi/README.md)(钉 revision + `.baseline.sha256` 钉 Hash),Rust 侧数值经 `cargo xtask gen-contracts` 从镜像生成,测试与镜像互证;ErrorCode 与 Capability **键**的数值权威只有 `ids/index.json`(Capability 键空间由 D-015 裁决,ADR-040 §7.1,仓内私有键值表即违规),Capability **bit**(掩码/计数与 bit 位,D-015 未裁)与非 `linux-x86_64-glibc` 布局(D-016)保持不绑定。 - 性能改动记录吞吐、p95/p99、分配、峰值内存、硬件/构建配置和结果确定性范围。 diff --git a/crates/lumio-contract-types/src/generated.rs b/crates/lumio-contract-types/src/generated.rs index 4c21be0..b327650 100644 --- a/crates/lumio-contract-types/src/generated.rs +++ b/crates/lumio-contract-types/src/generated.rs @@ -10,7 +10,8 @@ //! four indices, never the Rust/C# generated packages. //! //! Still deliberately unbound (treated as absent, not inferred): -//! - `capability_bits` semantics and any bit position (D-015 pending); +//! - `capability_bits` semantics and any bit position (D-015 adjudicated the +//! capability *key space* only, ADR-040 §7.1; mask-vs-count stays open); //! - any layout profile other than `linux-x86_64-glibc` (D-016 pending); //! - an `OperationId` namespace (does not exist; identity is the published //! (`apiTable[].name`, `slots[].slotIndex`) pair). @@ -26,13 +27,13 @@ pub(crate) const GENERATED_CONTRACT_REVISION: &str = ARCHITECTURE_BASELINE_ID; const ROOT_ABI_BUNDLE_ID: &str = "root-abi-v1"; const ROOT_ABI_BUNDLE_DIGEST: &str = - "03ca75361fed3ca95f8efd55af2e311ea8300b2635b590ae6d46394d58bc6a39"; + "02dce705a9a6fe7a437ed2e4137b03de7341ed614f30f10b614659c5226184a7"; const ROOT_ABI_HEADER_DIGEST: &str = - "040451bbde5a4dec3726be5f5a7be4bb934c3f68a1ca87f9c55559cae738efc7"; + "fa2aaca231b80ac5118dc13d3f4a4a50a1c16c844f09f8e2ac8d723af11c4352"; const ROOT_ABI_COMPILER_NAME: &str = "lumio-abi-compiler"; const ROOT_ABI_COMPILER_VERSION: &str = "1.0.0"; const ROOT_ABI_COMPILER_DIGEST: &str = - "217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290"; + "0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff"; const ROOT_ABI_INPUT_HASH: &str = "696a58d0525b897b549dd1e432166ae1020835902a5984221a8e60d5d8285bb3"; const ROOT_ABI_LAYOUT_PROFILE_ID: &str = "linux-x86_64-glibc"; @@ -121,11 +122,53 @@ pub struct ArchitectureOperationId { _private: (), } -/// Capability-bit newtype. Uninhabited until D-015 lands: V1 freezes neither -/// mask-vs-count semantics nor any bit position, and the ID Registry -/// `Capability` numerics are CoreEngine package-capability enumeration -/// ordinals, not bit positions — deriving a key from either is forbidden -/// (ADR-040 §7). +/// One registered `Capability` value. D-015 (ADR-040 §7.1) adjudicated the +/// key space: `ids/index.json` is the sole authority and the architecture +/// generator its sole emitter, so instances exist only in the generated +/// registry tables and no caller can mint an unregistered key. +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct ArchitectureCapabilityKey { + id: &'static str, + numeric: u32, + status: &'static str, +} + +impl ArchitectureCapabilityKey { + /// Only the generated registry tables construct instances. + pub(crate) const fn new(id: &'static str, numeric: u32, status: &'static str) -> Self { + Self { + id, + numeric, + status, + } + } + + /// Registered id string, e.g. `"VoxelSpatial"`. + pub const fn id(self) -> &'static str { + self.id + } + + /// Registered numeric: a 1-based enumeration ordinal, **not** a bit + /// position (D-015 froze the key space only). + pub const fn numeric(self) -> u32 { + self.numeric + } + + /// Published lifecycle status, `"Active"` or `"Reserved"`. + pub const fn status(self) -> &'static str { + self.status + } + + /// Whether the published status is `Active`. + pub fn is_active(self) -> bool { + self.status == "Active" + } +} + +/// Capability-bit newtype. Uninhabited: D-015 adjudicated the key space only +/// (see `ArchitectureCapabilityKey`). V1 still freezes neither mask-vs-count +/// semantics for `capability_bits` nor any bit position, so reading a key is +/// allowed and deriving a bit is not (ADR-040 §7 / §7.1). #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct CapabilityBits { _private: (), diff --git a/crates/lumio-contract-types/src/generated_data.rs b/crates/lumio-contract-types/src/generated_data.rs index ac96c3b..71bf8b5 100644 --- a/crates/lumio-contract-types/src/generated_data.rs +++ b/crates/lumio-contract-types/src/generated_data.rs @@ -4,7 +4,7 @@ //! (upstream revision in that directory's README). Regenerate with //! `cargo xtask gen-contracts` after a mirror update; commit together. -use crate::generated::ArchitectureErrorCode; +use crate::generated::{ArchitectureCapabilityKey, ArchitectureErrorCode}; use crate::layout::{AbiStructGolden, AbiTypeGolden}; pub(crate) const ABI_POINTER_BYTES: u32 = 8; @@ -105,3 +105,16 @@ pub(crate) const ERROR_CODES: &[ArchitectureErrorCode] = &[ ArchitectureErrorCode::new("PanicBoundary", 1052), ArchitectureErrorCode::new("InternalInvariant", 1053), ]; + +#[rustfmt::skip] +pub(crate) const CAPABILITY_KEYS: &[ArchitectureCapabilityKey] = &[ + ArchitectureCapabilityKey::new("Native", 1, "Active"), + ArchitectureCapabilityKey::new("HybridCLR", 2, "Reserved"), + ArchitectureCapabilityKey::new("ReferenceVoxel", 3, "Active"), + ArchitectureCapabilityKey::new("VoxelSnapshot", 4, "Active"), + ArchitectureCapabilityKey::new("VoxelStreaming", 5, "Active"), + ArchitectureCapabilityKey::new("VoxelSpatial", 6, "Active"), + ArchitectureCapabilityKey::new("VoxelMeshCollision", 7, "Active"), + ArchitectureCapabilityKey::new("VoxelAllResident", 8, "Active"), + ArchitectureCapabilityKey::new("VoxelVolatileChunks", 9, "Active"), +]; diff --git a/crates/lumio-contract-types/src/lib.rs b/crates/lumio-contract-types/src/lib.rs index b2913df..72993c4 100644 --- a/crates/lumio-contract-types/src/lib.rs +++ b/crates/lumio-contract-types/src/lib.rs @@ -7,9 +7,11 @@ //! 架构源已发布 baseline id `LGE-V1.4-2026-08-27` 与 ADR-040 Root ABI bundle; //! 本仓登记为该 bundle 的 consumer,按 ADR-040 §7 直接绑定其 C Header 与四个 //! 索引(字节级镜像见 `docs/architecture/abi/`),**不**消费 Rust/C# 生成包。 -//! ErrorCode 数值权威只有 `ids/index.json`(含 ADR-046 kernel band); -//! Capability bit 语义(D-015)、非 `linux-x86_64-glibc` 布局档(D-016)与 -//! OperationId(不存在,B-ABI-004 不适用)保持不绑定,一律不得手写。 +//! ErrorCode 与 Capability **键**的数值权威只有 `ids/index.json`(分别含 +//! ADR-046 kernel band 与 D-015 裁决的键空间,ADR-040 §7.1); +//! Capability bit 语义(D-015 只裁键空间,掩码/计数与 bit 位仍未冻结)、 +//! 非 `linux-x86_64-glibc` 布局档(D-016)与 OperationId(不存在, +//! B-ABI-004 不适用)保持不绑定,一律不得手写。 #![forbid(unsafe_code)] @@ -19,8 +21,9 @@ pub mod layout; pub mod registry; pub use generated::{ - AbiVersion, ArchitectureErrorCode, ArchitectureOperationId, CapabilityBits, ContractMismatch, - LumioBuffer, LumioCoreConfigV1, LumioHandle, LumioStatus, RootAbiBinding, StructSize, - abi_version, architecture_baseline_id, root_abi_binding, verify_generated_contract_revision, - verify_generated_contract_revision_against, verify_root_abi_bundle_digest_against, + AbiVersion, ArchitectureCapabilityKey, ArchitectureErrorCode, ArchitectureOperationId, + CapabilityBits, ContractMismatch, LumioBuffer, LumioCoreConfigV1, LumioHandle, LumioStatus, + RootAbiBinding, StructSize, abi_version, architecture_baseline_id, root_abi_binding, + verify_generated_contract_revision, verify_generated_contract_revision_against, + verify_root_abi_bundle_digest_against, }; diff --git a/crates/lumio-contract-types/src/registry.rs b/crates/lumio-contract-types/src/registry.rs index 1d78afc..a023f34 100644 --- a/crates/lumio-contract-types/src/registry.rs +++ b/crates/lumio-contract-types/src/registry.rs @@ -1,21 +1,25 @@ //! Read-only registry queries over the published ID Registry. //! -//! `ids/index.json` is the sole numeric authority (ADR-040 §7); the table -//! consumed here is generated from its byte-pinned mirror by +//! `ids/index.json` is the sole numeric authority (ADR-040 §7); the tables +//! consumed here are generated from its byte-pinned mirror by //! `cargo xtask gen-contracts`, so no numeric in this crate is hand-written. -//! Only the `ErrorCode` namespace is bound: +//! The `ErrorCode` and `Capability` namespaces are bound: //! -//! - `Capability` numerics are CoreEngine package-capability enumeration -//! ordinals, not bit positions; deriving any kernel capability key from -//! them is forbidden until D-015 lands, so they stay unbound here. +//! - `Capability` keys are bound since D-015 (ADR-040 §7.1): the registry is +//! the key-space authority and the architecture generator its sole +//! emitter, so a repository-private key table is a violation. The numerics +//! are 1-based enumeration ordinals, **not** bit positions — reading a key +//! is allowed, deriving a bit is not, and `capability_bits` stays unbound. //! - No `OperationId` namespace exists or is reserved (B-ABI-004 adjudicated //! not applicable): the public identity of a callable operation is the //! published (`apiTable[].name`, `slots[].slotIndex`) pair. //! - `MessageType` / `FaultClass` are GameRuntime-owned and outside this //! repository's consumption surface. -use crate::generated::{ArchitectureErrorCode, ArchitectureOperationId, CapabilityBits}; -use crate::generated_data::ERROR_CODES; +use crate::generated::{ + ArchitectureCapabilityKey, ArchitectureErrorCode, ArchitectureOperationId, CapabilityBits, +}; +use crate::generated_data::{CAPABILITY_KEYS, ERROR_CODES}; /// Architecture error codes from the generated registry, in published order /// (includes the ADR-046 kernel status band). @@ -35,8 +39,21 @@ pub fn operation_ids() -> impl Iterator { core::iter::empty() } -/// Capability bit entries. Empty until D-015 freezes the `capability_bits` -/// semantics and bit assignment (see module docs). +/// Registered capability keys from the generated registry, in published +/// order (ADR-040 §7.1); includes `Reserved` values, whose status callers +/// must honour rather than infer. +pub fn capability_keys() -> impl Iterator { + CAPABILITY_KEYS.iter().copied() +} + +/// Look up one registered capability key by its published id string. +pub fn capability_key(id: &str) -> Option { + CAPABILITY_KEYS.iter().copied().find(|key| key.id() == id) +} + +/// Capability bit entries. Still empty after D-015: the adjudication froze +/// the key space only, leaving `capability_bits` mask-vs-count semantics and +/// every bit position unbound (see module docs). pub fn capability_bits() -> impl Iterator { core::iter::empty() } diff --git a/crates/lumio-contract-types/tests/capability_keys_bind_registry.rs b/crates/lumio-contract-types/tests/capability_keys_bind_registry.rs new file mode 100644 index 0000000..d04e2cd --- /dev/null +++ b/crates/lumio-contract-types/tests/capability_keys_bind_registry.rs @@ -0,0 +1,165 @@ +mod common; + +use lumio_contract_types::registry; +use lumio_contract_types::{ArchitectureCapabilityKey, CapabilityBits}; + +/// SCREAMING_SNAKE projection of a registered id, matching the published +/// header macro spelling (`HybridCLR` -> `HYBRID_CLR`). +fn screaming(id: &str) -> String { + let chars: Vec = id.chars().collect(); + let mut out = String::new(); + for (i, c) in chars.iter().enumerate() { + if i > 0 + && c.is_ascii_uppercase() + && (!chars[i - 1].is_ascii_uppercase() + || chars.get(i + 1).is_some_and(char::is_ascii_lowercase)) + { + out.push('_'); + } + out.push(c.to_ascii_uppercase()); + } + out +} + +fn header_defines() -> Vec<(String, u32)> { + let path = common::mirror_path("lumio_core.h"); + let body = std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("read mirror {}: {e}", path.display())); + let mut out = Vec::new(); + for line in body.lines() { + let Some(rest) = line.strip_prefix("#define LUMIO_CAPABILITY_") else { + continue; + }; + let mut parts = rest.split_whitespace(); + let (Some(name), Some(value)) = (parts.next(), parts.next()) else { + continue; + }; + let numeric: u32 = value + .trim_end_matches('u') + .parse() + .unwrap_or_else(|e| panic!("bad capability define `{line}`: {e}")); + out.push((name.to_string(), numeric)); + } + out +} + +/// D-015 (ADR-040 §7.1): `ids/index.json` is the sole authority for the +/// capability key space and the architecture generator is its sole emitter. +/// The bound table must agree with the registry mirror AND with the header +/// projection, value for value — a repository-private key table is a +/// violation, so nothing here may be hand-written. +#[test] +fn capability_keys_bind_registry() { + let ids = common::parse_mirror("ids-index.json"); + let capability_ns = ids + .get("namespaces") + .as_arr() + .iter() + .find(|ns| ns.get("namespace").as_str() == "Capability") + .expect("mirror Capability namespace"); + assert_eq!(capability_ns.get("owner").as_str(), "Architecture"); + + let mirror: Vec<(&str, i64, &str)> = capability_ns + .get("values") + .as_arr() + .iter() + .map(|v| { + let status = v.get("status").as_str(); + assert!( + status == "Active" || status == "Reserved", + "unexpected Capability status {status}" + ); + (v.get("id").as_str(), v.get("numeric").as_i64(), status) + }) + .collect(); + + let bound: Vec = registry::capability_keys().collect(); + assert_eq!( + bound.len(), + mirror.len(), + "bound table must carry every published Capability value" + ); + for (key, (mirror_id, mirror_numeric, mirror_status)) in bound.iter().zip(&mirror) { + assert_eq!(key.id(), *mirror_id); + assert_eq!(i64::from(key.numeric()), *mirror_numeric); + assert_eq!(key.status(), *mirror_status); + assert!( + key.numeric() > 0, + "capability numerics are 1-based ordinals" + ); + } + + for (i, a) in bound.iter().enumerate() { + for b in &bound[i + 1..] { + assert_ne!(a.id(), b.id(), "duplicate Capability id"); + assert_ne!(a.numeric(), b.numeric(), "duplicate Capability numeric"); + } + } + + for key in &bound { + assert_eq!(registry::capability_key(key.id()), Some(*key)); + } + assert_eq!(registry::capability_key("NotARegisteredCapability"), None); +} + +/// The published C header is a projection of the same authority; the bound +/// table must agree with it name for name, count included. Regenerating one +/// side without the other is drift, not a warning. +#[test] +fn capability_keys_match_header_projection() { + let defines = header_defines(); + let bound: Vec = registry::capability_keys().collect(); + + let count = defines + .iter() + .find(|(name, _)| name == "COUNT") + .map(|(_, v)| *v) + .expect("header publishes LUMIO_CAPABILITY_COUNT"); + assert_eq!( + count as usize, + bound.len(), + "LUMIO_CAPABILITY_COUNT must equal the bound key count" + ); + + for key in &bound { + let expected = screaming(key.id()); + let (_, numeric) = defines + .iter() + .find(|(name, _)| *name == expected) + .unwrap_or_else(|| panic!("header missing LUMIO_CAPABILITY_{expected}")); + assert_eq!( + *numeric, + key.numeric(), + "header LUMIO_CAPABILITY_{expected} disagrees with the registry" + ); + } + + // Every define that is a key must be backed by a registered value: the + // header may not carry a capability the registry does not publish. + // `COUNT` is the cardinality and `BITS` is the `capability_bits` scalar — + // neither is a key, and `BITS` in particular stays outside the key space + // because D-015 left mask-vs-count and bit assignment unfrozen. + for (name, _) in defines.iter().filter(|(n, _)| n != "COUNT" && n != "BITS") { + assert!( + bound.iter().any(|k| screaming(k.id()) == *name), + "header publishes LUMIO_CAPABILITY_{name} with no registered key" + ); + } + assert!( + defines.iter().any(|(name, _)| name == "BITS"), + "LUMIO_CAPABILITY_BITS must stay published as a non-key scalar" + ); + assert!( + !bound.iter().any(|k| screaming(k.id()) == "BITS"), + "LUMIO_CAPABILITY_BITS must never be bound as a capability key" + ); +} + +/// D-015 adjudicated the key space only. Mask-vs-count semantics and any bit +/// assignment stay unfrozen, so the bit surface must remain empty: reading a +/// key is allowed, deriving a bit is not. +#[test] +fn capability_bits_stay_unbound() { + let bits: Vec = registry::capability_bits().collect(); + assert_eq!(bits.len(), 0); +} diff --git a/crates/lumio-kernel/src/capability/mod.rs b/crates/lumio-kernel/src/capability/mod.rs index 2e47d4b..31f7d6e 100644 --- a/crates/lumio-kernel/src/capability/mod.rs +++ b/crates/lumio-kernel/src/capability/mod.rs @@ -1,5 +1,8 @@ //! Static capability set, configured resource limits, and runtime status. -//! Public architecture bits remain blocked (B-ABI-002). +//! +//! Capability **keys** are projected from the generated architecture registry +//! (D-015 / ADR-040 §7.1). Capability **bits** remain blocked (B-ABI-002): +//! `capability_bits` mask-vs-count semantics and bit assignment are unfrozen. mod limits; mod runtime; diff --git a/crates/lumio-kernel/src/capability/static_set.rs b/crates/lumio-kernel/src/capability/static_set.rs index e14bb95..d045add 100644 --- a/crates/lumio-kernel/src/capability/static_set.rs +++ b/crates/lumio-kernel/src/capability/static_set.rs @@ -1,6 +1,13 @@ //! Sorted unique static capability set. //! -//! Keys are local indices, not architecture capability bits (Blocked B-ABI-002). +//! Keys come from the generated architecture registry (ADR-040 §7.1, D-015): +//! `ids/index.json` owns the key space and the architecture generator is its +//! sole emitter, so this module maps published keys and never keeps a +//! repository-private key table. The numerics it carries are 1-based +//! enumeration ordinals, **not** bit positions — `capability_bits` +//! mask-vs-count semantics and every bit assignment stay unfrozen. + +use lumio_contract_types::{ArchitectureCapabilityKey, registry}; use crate::error::{ErrorCategory, ErrorDetail, KernelError}; @@ -8,11 +15,21 @@ use crate::error::{ErrorCategory, ErrorDetail, KernelError}; pub struct CapabilityKey(u32); impl CapabilityKey { - pub const fn from_local_index(v: u32) -> Self { - Self(v) + /// The only way to obtain a key: project a registered architecture + /// capability. There is no raw constructor, so no caller — inside this + /// crate or outside it — can mint a key the registry does not publish. + pub const fn from_registered(key: ArchitectureCapabilityKey) -> Self { + Self(key.numeric()) + } + + /// Look up a registered capability by its published id string, e.g. + /// `"VoxelSpatial"`. Unregistered ids have no key. + pub fn from_registry_id(id: &str) -> Option { + registry::capability_key(id).map(Self::from_registered) } - pub const fn as_local_index(self) -> u32 { + /// The registered enumeration ordinal this key projects. + pub const fn as_registry_numeric(self) -> u32 { self.0 } } diff --git a/crates/lumio-kernel/tests/require_missing_capability_fails.rs b/crates/lumio-kernel/tests/require_missing_capability_fails.rs index a9ec6de..386937f 100644 --- a/crates/lumio-kernel/tests/require_missing_capability_fails.rs +++ b/crates/lumio-kernel/tests/require_missing_capability_fails.rs @@ -5,10 +5,13 @@ use lumio_kernel::capability::{ }; use lumio_kernel::error::ErrorCategory; +fn key(id: &str) -> CapabilityKey { + CapabilityKey::from_registry_id(id).unwrap_or_else(|| panic!("registered capability {id}")) +} + #[test] fn require_missing_capability_fails() { - let static_caps = - StaticCapabilities::from_keys([CapabilityKey::from_local_index(1)]).expect("unique keys"); + let static_caps = StaticCapabilities::from_keys([key("Native")]).expect("unique keys"); let limits = ConfiguredLimits { max_handles: 1, max_native_bytes: 1, @@ -18,10 +21,10 @@ fn require_missing_capability_fails() { }; let source = CapabilitySource::new(static_caps, limits).expect("valid limits"); - assert!(source.require(CapabilityKey::from_local_index(1)).is_ok()); + assert!(source.require(key("Native")).is_ok()); let err = source - .require(CapabilityKey::from_local_index(2)) + .require(key("HybridCLR")) .expect_err("missing key must fail"); assert_eq!(err.category(), ErrorCategory::CapabilityUnavailable); } diff --git a/crates/lumio-kernel/tests/static_capabilities_are_sorted_unique.rs b/crates/lumio-kernel/tests/static_capabilities_are_sorted_unique.rs index 13672a3..b44e095 100644 --- a/crates/lumio-kernel/tests/static_capabilities_are_sorted_unique.rs +++ b/crates/lumio-kernel/tests/static_capabilities_are_sorted_unique.rs @@ -1,34 +1,72 @@ -//! T-capability-01: StaticCapabilities stores a sorted unique key set. +//! T-capability-01: StaticCapabilities stores a sorted unique key set, built +//! from keys projected out of the generated architecture registry. +use lumio_contract_types::registry; use lumio_kernel::capability::{CapabilityKey, StaticCapabilities}; use lumio_kernel::error::ErrorCategory; +fn key(id: &str) -> CapabilityKey { + CapabilityKey::from_registry_id(id).unwrap_or_else(|| panic!("registered capability {id}")) +} + #[test] fn static_capabilities_are_sorted_unique() { - let caps = StaticCapabilities::from_keys([ - CapabilityKey::from_local_index(3), - CapabilityKey::from_local_index(1), - CapabilityKey::from_local_index(2), - ]) - .expect("unsorted unique keys should be accepted"); + let caps = + StaticCapabilities::from_keys([key("ReferenceVoxel"), key("Native"), key("HybridCLR")]) + .expect("unsorted unique keys should be accepted"); let collected: Vec<_> = caps.iter().collect(); assert_eq!( collected, - vec![ - CapabilityKey::from_local_index(1), - CapabilityKey::from_local_index(2), - CapabilityKey::from_local_index(3), - ] + vec![key("Native"), key("HybridCLR"), key("ReferenceVoxel")], + "keys must come back in registered-ordinal order" ); - assert!(caps.contains(CapabilityKey::from_local_index(1))); - assert!(caps.contains(CapabilityKey::from_local_index(2))); - assert!(caps.contains(CapabilityKey::from_local_index(3))); - assert!(!caps.contains(CapabilityKey::from_local_index(4))); - - let err = StaticCapabilities::from_keys([ - CapabilityKey::from_local_index(1), - CapabilityKey::from_local_index(1), - ]) - .expect_err("duplicate keys should be rejected"); + assert!(caps.contains(key("Native"))); + assert!(caps.contains(key("HybridCLR"))); + assert!(caps.contains(key("ReferenceVoxel"))); + assert!(!caps.contains(key("VoxelSnapshot"))); + + let err = StaticCapabilities::from_keys([key("Native"), key("Native")]) + .expect_err("duplicate keys should be rejected"); assert_eq!(err.category(), ErrorCategory::InvalidArgument); } + +/// The generated conversion half of T-capability-01: every published key maps +/// one-to-one onto a `CapabilityKey`, with no repository-private key table in +/// between (D-015 / ADR-040 §7.1). Same cross-check the architecture gate +/// runs, expressed against this repository's mapping. +#[test] +fn capability_keys_project_the_generated_registry() { + let published: Vec<_> = registry::capability_keys().collect(); + assert!( + !published.is_empty(), + "the generated registry must publish capability keys" + ); + + let mapped: Vec = published + .iter() + .copied() + .map(CapabilityKey::from_registered) + .collect(); + for (key, publication) in mapped.iter().zip(&published) { + assert_eq!(key.as_registry_numeric(), publication.numeric()); + assert_eq!( + CapabilityKey::from_registry_id(publication.id()), + Some(*key), + "id lookup must agree with the registered projection" + ); + } + + // The whole published set is a valid static set: distinct keys, and every + // one of them present after sorting. + let caps = StaticCapabilities::from_keys(mapped.iter().copied()) + .expect("published keys are unique, so the full set must be accepted"); + assert_eq!(caps.iter().count(), published.len()); + for key in &mapped { + assert!(caps.contains(*key)); + } + + assert_eq!( + CapabilityKey::from_registry_id("NotARegisteredCapability"), + None + ); +} diff --git a/docs/architecture/.baseline.sha256 b/docs/architecture/.baseline.sha256 index f035929..b3dd55e 100644 --- a/docs/architecture/.baseline.sha256 +++ b/docs/architecture/.baseline.sha256 @@ -1,5 +1,5 @@ f1d36acf33a1f5e8326a9e58d609fcf7d9fa85177f9b5b60bb3f4742c1afebd0 docs/architecture/LumioGameEngine_Architecture_v1.4.md 515c1626163d935a85fabf04afa743d38b4ba0fb49a7bf38602d2155f4f097f1 docs/architecture/abi/ids-index.json -040451bbde5a4dec3726be5f5a7be4bb934c3f68a1ca87f9c55559cae738efc7 docs/architecture/abi/lumio_core.h -d33c2c90bee1318cb0829c567259593f9b1fd388bc7691604fee521929780fbb docs/architecture/abi/packages-index.json -03ca75361fed3ca95f8efd55af2e311ea8300b2635b590ae6d46394d58bc6a39 docs/architecture/abi/root-abi-bundle.json +fa2aaca231b80ac5118dc13d3f4a4a50a1c16c844f09f8e2ac8d723af11c4352 docs/architecture/abi/lumio_core.h +a47ca0c41cddf4f9c540c98687054944b15babbfab8341515221aeb248f8290a docs/architecture/abi/packages-index.json +02dce705a9a6fe7a437ed2e4137b03de7341ed614f30f10b614659c5226184a7 docs/architecture/abi/root-abi-bundle.json diff --git a/docs/architecture/abi/README.md b/docs/architecture/abi/README.md index 19387c5..0cefbf7 100644 --- a/docs/architecture/abi/README.md +++ b/docs/architecture/abi/README.md @@ -5,7 +5,7 @@ ## 钉住的上游 revision(钉 revision,不钉分支名) - 上游仓库:`LumioGameEngineArchitecture`(`https://github.com/LumioGames/LumioGameEngineArchitecture.git`) -- 镜像 revision:`origin/main` 提交 `1f2ead332b3dfc3042e1495bfbe6febb8699df7e`(含内容提交 `5c222c4`,2026-08-28) +- 镜像 revision:`origin/main` 提交 `3287bba48fc49bf89782525ad55f45b0d77ae6f1`(含内容提交 `99f94fb`,ADR-048 generated surface,2026-08-29) - 架构基线:`LGE-V1.4-2026-08-27` ## 文件清单与上游路径 @@ -23,7 +23,8 @@ - 数值权威只有 `ids/index.json`;生成包只发布 id 字符串,从生成包读 ordinal 等于读未发布之物。 - V1 布局 Golden 只发布 `linux-x86_64-glibc` 一档,其余平台布局不得断言(D-016 待裁决)。 -- `capability_bits` 是掩码还是计数、以及任何 bit 位指派,V1 均未冻结;`Capability` 命名空间 numeric 是枚举序号,不是 bit 位(D-015 待裁决)。 +- Capability **键空间**已由 D-015 裁决(ADR-040 §7.1):`ids/index.json` 的 `Capability` 命名空间是唯一权威,架构生成器是唯一发射方,Header 的 `LUMIO_CAPABILITY_` 是该权威的投影;下游消费生成物,仓内私有键值表即违规。 +- 仍未冻结:`capability_bits` 是掩码还是计数、以及任何 bit 位指派。`Capability` numeric 是枚举序号,**读键可以,推 bit 不行**。 - 不存在 `OperationId` 命名空间;公共操作身份是 (`apiTable[].name`, `slots[].slotIndex`)。 - 跨仓 Root 符号(`LUMIO_ENTRY_SYMBOL`)由 CoreEngine `root-abi`/`composition` 独占导出;本仓发布物不得导出。 diff --git a/docs/architecture/abi/lumio_core.h b/docs/architecture/abi/lumio_core.h index f783427..93191ea 100644 --- a/docs/architecture/abi/lumio_core.h +++ b/docs/architecture/abi/lumio_core.h @@ -18,6 +18,20 @@ extern "C" { #define LUMIO_SYMBOL_PREFIX "lumio_" #define LUMIO_CAPABILITY_BITS 7u +/* ADR-040 section 7 (D-015): capability keys projected from the ID Registry. */ +/* ids/index.json is the authority; these are enumeration keys, NOT bit + positions -- LUMIO_CAPABILITY_BITS semantics stay unfrozen. */ +#define LUMIO_CAPABILITY_NATIVE 1u +#define LUMIO_CAPABILITY_HYBRID_CLR 2u +#define LUMIO_CAPABILITY_REFERENCE_VOXEL 3u +#define LUMIO_CAPABILITY_VOXEL_SNAPSHOT 4u +#define LUMIO_CAPABILITY_VOXEL_STREAMING 5u +#define LUMIO_CAPABILITY_VOXEL_SPATIAL 6u +#define LUMIO_CAPABILITY_VOXEL_MESH_COLLISION 7u +#define LUMIO_CAPABILITY_VOXEL_ALL_RESIDENT 8u +#define LUMIO_CAPABILITY_VOXEL_VOLATILE_CHUNKS 9u +#define LUMIO_CAPABILITY_COUNT 9u + typedef int32_t lumio_status_t; typedef struct lumio_handle_t { diff --git a/docs/architecture/abi/packages-index.json b/docs/architecture/abi/packages-index.json index 9081b8c..9ea2d54 100644 --- a/docs/architecture/abi/packages-index.json +++ b/docs/architecture/abi/packages-index.json @@ -1 +1 @@ -{"artifacts":[{"artifactId":"protocol-permission-validator-rust","artifactKind":"ProtocolPermissionValidator","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"rust","outputHash":"fdd1bdc57b95447e09f15008069bd6c6b6eb355897f075eddb0d71770feee5e2","packagePath":"rust/lumio-gen-protocol-permission-validator/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"protocol-permission-validator-csharp","artifactKind":"ProtocolPermissionValidator","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"csharp","outputHash":"6bd347ff929c0446ce801bfae2b22f9fc0d65812f47c1c9e1ea8270ba03750a2","packagePath":"csharp/Lumio.Gen.ProtocolPermissionValidator/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"mapping-table-rust","artifactKind":"MappingTable","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"rust","outputHash":"bd248c0442f84c5ac8c0bf18ab13f855bc610056f5be106a124ed7bed85bf56c","packagePath":"rust/lumio-gen-mapping-table/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"mapping-table-csharp","artifactKind":"MappingTable","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"csharp","outputHash":"c3f8e85858e2048ba31fe945f547ca68448eddbbd823c32569b47400307e0e59","packagePath":"csharp/Lumio.Gen.MappingTable/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"canonical-serializer-rust","artifactKind":"CanonicalSerializer","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"rust","outputHash":"fd9720649389fd6f49f63ecac2727c98237492c8637c4f2d15a97252d7c45e50","packagePath":"rust/lumio-gen-canonical-serializer/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"canonical-serializer-csharp","artifactKind":"CanonicalSerializer","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"csharp","outputHash":"b16885850b80278870c09ef6bdf2dd41a5f67f54574800fbd44ea23aed409bbf","packagePath":"csharp/Lumio.Gen.CanonicalSerializer/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"language-binding-rust","artifactKind":"LanguageBinding","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"rust","outputHash":"6b8e2a390e6beb9bd918ea5db58a61b7483555134b65c1dc04ee7373a0effeea","packagePath":"rust/lumio-gen-language-binding/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"language-binding-csharp","artifactKind":"LanguageBinding","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"csharp","outputHash":"dae2beac6c36b8715a1ebe61dc36a5082299d9525666ea3877409b1accff381d","packagePath":"csharp/Lumio.Gen.LanguageBinding/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"contract-types-rust","artifactKind":"ContractTypes","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"rust","outputHash":"5c686850855bdf111c5ebf6128cd0dfba14a46dd74eaeabe3cf7ffe2cde23b34","packagePath":"rust/lumio-gen-contract-types/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"contract-types-csharp","artifactKind":"ContractTypes","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"csharp","outputHash":"69a2f24659520258fe4cc594c77e26a99ddac254fab6662e4cf6c58d594945c4","packagePath":"csharp/Lumio.Gen.ContractTypes/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"contract-runtime-rust","artifactKind":"ContractRuntime","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"rust","outputHash":"3f9357242b67ce513cd3e1c102f9e96d7402922ba0a04ec976ed70a60d45cc52","packagePath":"rust/lumio-gen-contract-runtime/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"contract-runtime-csharp","artifactKind":"ContractRuntime","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","language":"csharp","outputHash":"fbc3e82cb63cbca338e85ccef045dab0f7604ede3910f695fb17b9e70e23a4dd","packagePath":"csharp/Lumio.Gen.ContractRuntime/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1}],"baselineId":"LGE-V1.4-2026-08-27","blocked":[{"id":"D-009","reason":"protocol-dispatch not frozen"},{"id":"D-011","reason":"Auth wire not frozen"}],"canonicalDigest":{"consumers":["LumioCoreEngine"],"digestAlgorithm":{"framing":"PrefixFreeOverCanonicalBytes","name":"SHA-256"},"formId":"CanonicalJsonV1","goldenCount":10,"profileDigest":"4bf1976f6811357bd74b922358afd6f4c1e6d68fa925ab5249139bf781ecef46","profileId":"canonical-digest-v1","profilePath":"canonical/canonical-digest-profile.json"},"compilerHash":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","evidence":{"consumers":["LumioCoreEngine"],"profileCount":3,"profileDigest":"61b635c60c5ac7bad6155fdbfd52feb91b1ac80c21924cddc333331e7fb724e3","profileId":"evidence-profile-v1","profilePath":"evidence/evidence-profile.json"},"inputHash":"4e2542521219896cd1534e8e3064fe01e8737d62df5c846cca2033f9548f37e0","loader":{"consumers":["LumioCoreEngine"],"errorPriorityLength":10,"profileDigest":"9eefca4d1a809755db9614aa1924e247f3d9f60ef942357355408a1a1bce975f","profileId":"loader-profile-v1","profilePath":"loader/loader-profile.json"},"rootAbi":{"bundleDigest":"03ca75361fed3ca95f8efd55af2e311ea8300b2635b590ae6d46394d58bc6a39","bundleId":"root-abi-v1","bundlePath":"abi/root-abi-bundle.json","compiler":{"digest":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","name":"lumio-abi-compiler","version":"1.0.0"},"consumers":["LumioCoreEngine","LumioNativeCore"],"inputHash":"696a58d0525b897b549dd1e432166ae1020835902a5984221a8e60d5d8285bb3","layoutProfileId":"linux-x86_64-glibc","outputFiles":[{"digest":"040451bbde5a4dec3726be5f5a7be4bb934c3f68a1ca87f9c55559cae738efc7","path":"abi/lumio_core.h","role":"CHeader"},{"digest":"5e81bdfb6e879d849e2cb77a847a07167e5a459f2f23fd43f07609e726043bec","path":"rust/lumio-gen-language-binding/src/root_abi.rs","role":"RustBinding"},{"digest":"d89ff35434438773055ce4108b9f04ef6ff2b42335101249163f65c734975cd1","path":"csharp/Lumio.Gen.LanguageBinding/RootAbi.cs","role":"CSharpBinding"}]},"schemaEpoch":1,"stateMachineCount":12,"stateMachineIds":["ClientReplicaSession","CoreEngineLoader","CrossWorldTxn","EcsCommandBuffer","GameplayScopeActivation","GasAbility","GasEffect","ReleasePool","SimulationSession","VoxelChunkResidency","VoxelSnapshotCapture","WorldSlotHost"],"trust":{"consumers":["LumioCoreEngine"],"profileDigest":"fa4bd14550d7e21e9e1996e21e5e157b708c08a6692754d859e13f615b4d5399","profileId":"trust-profile-v1","profilePath":"trust/trust-profile.json","signatureProfileId":"LumioSignatureV1","trustDomain":"Test","vectorCount":8}} +{"artifacts":[{"artifactId":"protocol-permission-validator-rust","artifactKind":"ProtocolPermissionValidator","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"rust","outputHash":"f574640ce566b522e591c60d93fefe8d33ccfd1376afddae8f3e884cd07d62c9","packagePath":"rust/lumio-gen-protocol-permission-validator/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"protocol-permission-validator-csharp","artifactKind":"ProtocolPermissionValidator","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"csharp","outputHash":"44530d3a8918c4c2cfde90a6725b2158131422124104f4c349050aad3fca7abd","packagePath":"csharp/Lumio.Gen.ProtocolPermissionValidator/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"mapping-table-rust","artifactKind":"MappingTable","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"rust","outputHash":"bd248c0442f84c5ac8c0bf18ab13f855bc610056f5be106a124ed7bed85bf56c","packagePath":"rust/lumio-gen-mapping-table/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"mapping-table-csharp","artifactKind":"MappingTable","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"csharp","outputHash":"2fe00142627ceec3028c35bccd56a8bc8af3ca725f7150286c4f610f5e0f7ff0","packagePath":"csharp/Lumio.Gen.MappingTable/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"canonical-serializer-rust","artifactKind":"CanonicalSerializer","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"rust","outputHash":"4a52459491dba3699ccaf9c28e9b2b606eaa976b7eb95e94e071235417356772","packagePath":"rust/lumio-gen-canonical-serializer/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"canonical-serializer-csharp","artifactKind":"CanonicalSerializer","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"csharp","outputHash":"eec4d0d05ba438e2902b5728a9e992bd2d990a11b3ffcc01ce659ddc541598db","packagePath":"csharp/Lumio.Gen.CanonicalSerializer/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"language-binding-rust","artifactKind":"LanguageBinding","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"rust","outputHash":"054e153ae37748823d26bfccc674f03ea37c9633895da0b68160e6adaf7e5b58","packagePath":"rust/lumio-gen-language-binding/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"language-binding-csharp","artifactKind":"LanguageBinding","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"csharp","outputHash":"a48c7046565d15d71684bdd0228079aa1af15e4485afe88d58783d2c45454984","packagePath":"csharp/Lumio.Gen.LanguageBinding/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"contract-types-rust","artifactKind":"ContractTypes","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"rust","outputHash":"10dc3360e3f117ce9a5de79b59ba21165b596f226c495660825e1e57db924461","packagePath":"rust/lumio-gen-contract-types/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"contract-types-csharp","artifactKind":"ContractTypes","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"csharp","outputHash":"18d22646b53f06493ebf90d7dd7f4557c3e97b9ee56c4c9bb9fa53789cd135f4","packagePath":"csharp/Lumio.Gen.ContractTypes/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"contract-runtime-rust","artifactKind":"ContractRuntime","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"rust","outputHash":"3f9357242b67ce513cd3e1c102f9e96d7402922ba0a04ec976ed70a60d45cc52","packagePath":"rust/lumio-gen-contract-runtime/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1},{"artifactId":"contract-runtime-csharp","artifactKind":"ContractRuntime","baselineId":"LGE-V1.4-2026-08-27","compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","consumers":["LumioClient","LumioGame","LumioGameRuntime","LumioServer"],"forbiddenDependents":["LumioClient","LumioGame"],"implementationDependencies":[],"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","language":"csharp","outputHash":"546cb5690757d973b45cc997c9d09acf8543e2182dffe66e3cd763b300d1e38b","packagePath":"csharp/Lumio.Gen.ContractRuntime/","publisher":"LumioGameEngineArchitecture","schemaEpoch":1}],"baselineId":"LGE-V1.4-2026-08-27","binary":{"consumers":["LumioGame","LumioGameRuntime","LumioServer","LumioVoxelEngine"],"digestAlgorithm":{"digestInput":"EncodedBytesOnly","framing":"None","name":"SHA-256"},"formId":"LumioBinV1","goldenCount":6,"profileDigest":"03b3fab181d1ebbe73b2c853d569c0819f08309cf339848809f92100368d458e","profileId":"lumio-bin-v1","profilePath":"binary/lumio-bin-profile.json","rejectionCount":12},"blocked":[{"id":"D-009","reason":"protocol-dispatch not frozen"},{"id":"D-011","reason":"Auth wire not frozen"}],"canonicalDigest":{"consumers":["LumioCoreEngine"],"digestAlgorithm":{"framing":"PrefixFreeOverCanonicalBytes","name":"SHA-256"},"formId":"CanonicalJsonV1","goldenCount":10,"profileDigest":"4bf1976f6811357bd74b922358afd6f4c1e6d68fa925ab5249139bf781ecef46","profileId":"canonical-digest-v1","profilePath":"canonical/canonical-digest-profile.json"},"compilerHash":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","evidence":{"consumers":["LumioCoreEngine"],"profileCount":3,"profileDigest":"61b635c60c5ac7bad6155fdbfd52feb91b1ac80c21924cddc333331e7fb724e3","profileId":"evidence-profile-v1","profilePath":"evidence/evidence-profile.json"},"inputHash":"bb95d87078c83b40e5148f58d68aa7a1df7cded94d28657a0f11e4f1231c2ff9","loader":{"consumers":["LumioCoreEngine"],"errorPriorityLength":10,"profileDigest":"9eefca4d1a809755db9614aa1924e247f3d9f60ef942357355408a1a1bce975f","profileId":"loader-profile-v1","profilePath":"loader/loader-profile.json"},"rootAbi":{"bundleDigest":"02dce705a9a6fe7a437ed2e4137b03de7341ed614f30f10b614659c5226184a7","bundleId":"root-abi-v1","bundlePath":"abi/root-abi-bundle.json","compiler":{"digest":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","name":"lumio-abi-compiler","version":"1.0.0"},"consumers":["LumioCoreEngine","LumioNativeCore"],"inputHash":"696a58d0525b897b549dd1e432166ae1020835902a5984221a8e60d5d8285bb3","layoutProfileId":"linux-x86_64-glibc","outputFiles":[{"digest":"fa2aaca231b80ac5118dc13d3f4a4a50a1c16c844f09f8e2ac8d723af11c4352","path":"abi/lumio_core.h","role":"CHeader"},{"digest":"ddf60c5d18398bec0e7dfd42275fbc5c085a24431e495f7f71a03ded00bdcd20","path":"rust/lumio-gen-language-binding/src/root_abi.rs","role":"RustBinding"},{"digest":"08c80b5c7d9001dc6e6c4cce176a5bc26026277a937dc8e8614ead7706ca2110","path":"csharp/Lumio.Gen.LanguageBinding/RootAbi.cs","role":"CSharpBinding"}]},"schemaEpoch":1,"stateMachineCount":12,"stateMachineIds":["ClientReplicaSession","CoreEngineLoader","CrossWorldTxn","EcsCommandBuffer","GameplayScopeActivation","GasAbility","GasEffect","ReleasePool","SimulationSession","VoxelChunkResidency","VoxelSnapshotCapture","WorldSlotHost"],"trust":{"consumers":["LumioCoreEngine"],"profileDigest":"fa4bd14550d7e21e9e1996e21e5e157b708c08a6692754d859e13f615b4d5399","profileId":"trust-profile-v1","profilePath":"trust/trust-profile.json","signatureProfileId":"LumioSignatureV1","trustDomain":"Test","vectorCount":8}} diff --git a/docs/architecture/abi/root-abi-bundle.json b/docs/architecture/abi/root-abi-bundle.json index 1f19c86..5263dc9 100644 --- a/docs/architecture/abi/root-abi-bundle.json +++ b/docs/architecture/abi/root-abi-bundle.json @@ -1 +1 @@ -{"abi":{"abiVersion":1,"callingConvention":"C","capabilityBits":7,"endianness":"Little","entrySymbol":"lumio_core_get_api_v1","pointerWidth":64,"symbolPrefix":"lumio_"},"baselineId":"LGE-V1.4-2026-08-27","bundleId":"root-abi-v1","compiler":{"digest":"217437fd4755e1a339e2029838cc4a2d2fb305fa05520c8cfd10ea98cc2ff290","name":"lumio-abi-compiler","version":"1.0.0"},"inputHash":"696a58d0525b897b549dd1e432166ae1020835902a5984221a8e60d5d8285bb3","inputSet":["schemas/native-managed-abi.schema.json","fixtures/valid/native-managed-abi.json"],"layoutProfile":{"abiRuntime":"glibc","arch":"x86_64","maxAlignment":8,"os":"LinuxServer","pointerBytes":8,"rootHeaderBytes":16,"tableHeaderBytes":16,"targetProfileId":"linux-x86_64-glibc"},"outputFiles":[{"digest":"040451bbde5a4dec3726be5f5a7be4bb934c3f68a1ca87f9c55559cae738efc7","path":"abi/lumio_core.h","role":"CHeader"},{"digest":"5e81bdfb6e879d849e2cb77a847a07167e5a459f2f23fd43f07609e726043bec","path":"rust/lumio-gen-language-binding/src/root_abi.rs","role":"RustBinding"},{"digest":"d89ff35434438773055ce4108b9f04ef6ff2b42335101249163f65c734975cd1","path":"csharp/Lumio.Gen.LanguageBinding/RootAbi.cs","role":"CSharpBinding"}],"root":{"declaredStructSize":64,"fields":[{"c":"uint32_t","csharp":"uint","name":"abi_version","offset":0,"rust":"u32","size":4},{"c":"uint32_t","csharp":"uint","name":"struct_size","offset":4,"rust":"u32","size":4},{"c":"uint64_t","csharp":"ulong","name":"capability_bits","offset":8,"rust":"u64","size":8}],"minimumStructSize":32,"tables":[{"name":"lumio_core_api","offset":16},{"name":"lumio_voxel_api","offset":24}]},"schemaEpoch":1,"tables":[{"declaredStructSize":48,"fields":[{"c":"uint32_t","csharp":"uint","name":"version","offset":0,"rust":"u32","size":4},{"c":"uint32_t","csharp":"uint","name":"struct_size","offset":4,"rust":"u32","size":4},{"c":"uint64_t","csharp":"ulong","name":"reserved0","offset":8,"rust":"u64","size":8}],"functionCount":3,"minimumStructSize":48,"name":"lumio_core_api","reservedSlots":1,"slots":[{"cSignature":"lumio_status_t (*lumio_core_init)(const struct lumio_core_config_v1* config, lumio_handle_t out_context)","csharpSignature":"LumioStatus lumio_core_init(IntPtr config, LumioHandle out_context)","name":"lumio_core_init","offset":16,"params":[{"name":"config","typeRef":"struct:core_config:v1"},{"name":"out_context","typeRef":"handle:core_context"}],"returns":"status","rustSignature":"extern \"C\" fn(config: *const LumioCoreConfigV1, out_context: LumioHandle) -> LumioStatus","slotIndex":0},{"cSignature":"lumio_status_t (*lumio_core_shutdown)(lumio_handle_t context)","csharpSignature":"LumioStatus lumio_core_shutdown(LumioHandle context)","name":"lumio_core_shutdown","offset":24,"params":[{"name":"context","typeRef":"handle:core_context"}],"returns":"status","rustSignature":"extern \"C\" fn(context: LumioHandle) -> LumioStatus","slotIndex":1},{"cSignature":"lumio_status_t (*lumio_core_last_error_detail)(lumio_handle_t context, lumio_buffer_t out_detail)","csharpSignature":"LumioStatus lumio_core_last_error_detail(LumioHandle context, LumioBuffer out_detail)","name":"lumio_core_last_error_detail","offset":32,"params":[{"name":"context","typeRef":"handle:core_context"},{"name":"out_detail","typeRef":"buffer:out"}],"returns":"status","rustSignature":"extern \"C\" fn(context: LumioHandle, out_detail: LumioBuffer) -> LumioStatus","slotIndex":2}],"version":1},{"declaredStructSize":32,"fields":[{"c":"uint32_t","csharp":"uint","name":"version","offset":0,"rust":"u32","size":4},{"c":"uint32_t","csharp":"uint","name":"struct_size","offset":4,"rust":"u32","size":4},{"c":"uint64_t","csharp":"ulong","name":"reserved0","offset":8,"rust":"u64","size":8}],"functionCount":2,"minimumStructSize":32,"name":"lumio_voxel_api","reservedSlots":0,"slots":[{"cSignature":"lumio_status_t (*lumio_voxel_world_create)(lumio_handle_t context, const struct lumio_voxel_world_desc_v1* desc, lumio_handle_t out_world)","csharpSignature":"LumioStatus lumio_voxel_world_create(LumioHandle context, IntPtr desc, LumioHandle out_world)","name":"lumio_voxel_world_create","offset":16,"params":[{"name":"context","typeRef":"handle:core_context"},{"name":"desc","typeRef":"struct:voxel_world_desc:v1"},{"name":"out_world","typeRef":"handle:voxel_world"}],"returns":"status","rustSignature":"extern \"C\" fn(context: LumioHandle, desc: *const LumioVoxelWorldDescV1, out_world: LumioHandle) -> LumioStatus","slotIndex":0},{"cSignature":"lumio_status_t (*lumio_voxel_world_destroy)(lumio_handle_t world)","csharpSignature":"LumioStatus lumio_voxel_world_destroy(LumioHandle world)","name":"lumio_voxel_world_destroy","offset":24,"params":[{"name":"world","typeRef":"handle:voxel_world"}],"returns":"status","rustSignature":"extern \"C\" fn(world: LumioHandle) -> LumioStatus","slotIndex":1}],"version":1}],"typeMapping":[{"align":1,"c":"uint8_t","csharp":"byte","rust":"u8","size":1,"typeRef":"u8"},{"align":2,"c":"uint16_t","csharp":"ushort","rust":"u16","size":2,"typeRef":"u16"},{"align":4,"c":"uint32_t","csharp":"uint","rust":"u32","size":4,"typeRef":"u32"},{"align":8,"c":"uint64_t","csharp":"ulong","rust":"u64","size":8,"typeRef":"u64"},{"align":1,"c":"int8_t","csharp":"sbyte","rust":"i8","size":1,"typeRef":"i8"},{"align":2,"c":"int16_t","csharp":"short","rust":"i16","size":2,"typeRef":"i16"},{"align":4,"c":"int32_t","csharp":"int","rust":"i32","size":4,"typeRef":"i32"},{"align":8,"c":"int64_t","csharp":"long","rust":"i64","size":8,"typeRef":"i64"},{"align":4,"c":"float","csharp":"float","rust":"f32","size":4,"typeRef":"f32"},{"align":8,"c":"double","csharp":"double","rust":"f64","size":8,"typeRef":"f64"},{"align":4,"c":"uint32_t","csharp":"uint","rust":"u32","size":4,"typeRef":"bool32"},{"align":4,"c":"lumio_status_t","csharp":"LumioStatus","rust":"LumioStatus","size":4,"typeRef":"status"},{"align":8,"c":"lumio_handle_t","csharp":"LumioHandle","rust":"LumioHandle","size":16,"typeRef":"handle:"},{"align":8,"c":"lumio_buffer_t","csharp":"LumioBuffer","rust":"LumioBuffer","size":24,"typeRef":"buffer:in"},{"align":8,"c":"lumio_buffer_t","csharp":"LumioBuffer","rust":"LumioBuffer","size":24,"typeRef":"buffer:out"},{"align":8,"c":"lumio_buffer_t","csharp":"LumioBuffer","rust":"LumioBuffer","size":24,"typeRef":"buffer:inout"},{"align":8,"c":"const lumio__v*","csharp":"IntPtr","rust":"*const LumioV","size":8,"typeRef":"struct::v"},{"align":8,"c":"const lumio_*","csharp":"IntPtr","rust":"*const Lumio","size":8,"typeRef":"ptr:const:"},{"align":8,"c":"lumio_*","csharp":"IntPtr","rust":"*mut Lumio","size":8,"typeRef":"ptr:mut:"}]} +{"abi":{"abiVersion":1,"callingConvention":"C","capabilityBits":7,"endianness":"Little","entrySymbol":"lumio_core_get_api_v1","pointerWidth":64,"symbolPrefix":"lumio_"},"baselineId":"LGE-V1.4-2026-08-27","bundleId":"root-abi-v1","compiler":{"digest":"0aaf61d65153aadc4ddda1b36fa1b7bfb38373d52e8ba3299457cefe16864bff","name":"lumio-abi-compiler","version":"1.0.0"},"inputHash":"696a58d0525b897b549dd1e432166ae1020835902a5984221a8e60d5d8285bb3","inputSet":["schemas/native-managed-abi.schema.json","fixtures/valid/native-managed-abi.json"],"layoutProfile":{"abiRuntime":"glibc","arch":"x86_64","maxAlignment":8,"os":"LinuxServer","pointerBytes":8,"rootHeaderBytes":16,"tableHeaderBytes":16,"targetProfileId":"linux-x86_64-glibc"},"outputFiles":[{"digest":"fa2aaca231b80ac5118dc13d3f4a4a50a1c16c844f09f8e2ac8d723af11c4352","path":"abi/lumio_core.h","role":"CHeader"},{"digest":"ddf60c5d18398bec0e7dfd42275fbc5c085a24431e495f7f71a03ded00bdcd20","path":"rust/lumio-gen-language-binding/src/root_abi.rs","role":"RustBinding"},{"digest":"08c80b5c7d9001dc6e6c4cce176a5bc26026277a937dc8e8614ead7706ca2110","path":"csharp/Lumio.Gen.LanguageBinding/RootAbi.cs","role":"CSharpBinding"}],"root":{"declaredStructSize":64,"fields":[{"c":"uint32_t","csharp":"uint","name":"abi_version","offset":0,"rust":"u32","size":4},{"c":"uint32_t","csharp":"uint","name":"struct_size","offset":4,"rust":"u32","size":4},{"c":"uint64_t","csharp":"ulong","name":"capability_bits","offset":8,"rust":"u64","size":8}],"minimumStructSize":32,"tables":[{"name":"lumio_core_api","offset":16},{"name":"lumio_voxel_api","offset":24}]},"schemaEpoch":1,"tables":[{"declaredStructSize":48,"fields":[{"c":"uint32_t","csharp":"uint","name":"version","offset":0,"rust":"u32","size":4},{"c":"uint32_t","csharp":"uint","name":"struct_size","offset":4,"rust":"u32","size":4},{"c":"uint64_t","csharp":"ulong","name":"reserved0","offset":8,"rust":"u64","size":8}],"functionCount":3,"minimumStructSize":48,"name":"lumio_core_api","reservedSlots":1,"slots":[{"cSignature":"lumio_status_t (*lumio_core_init)(const struct lumio_core_config_v1* config, lumio_handle_t out_context)","csharpSignature":"LumioStatus lumio_core_init(IntPtr config, LumioHandle out_context)","name":"lumio_core_init","offset":16,"params":[{"name":"config","typeRef":"struct:core_config:v1"},{"name":"out_context","typeRef":"handle:core_context"}],"returns":"status","rustSignature":"extern \"C\" fn(config: *const LumioCoreConfigV1, out_context: LumioHandle) -> LumioStatus","slotIndex":0},{"cSignature":"lumio_status_t (*lumio_core_shutdown)(lumio_handle_t context)","csharpSignature":"LumioStatus lumio_core_shutdown(LumioHandle context)","name":"lumio_core_shutdown","offset":24,"params":[{"name":"context","typeRef":"handle:core_context"}],"returns":"status","rustSignature":"extern \"C\" fn(context: LumioHandle) -> LumioStatus","slotIndex":1},{"cSignature":"lumio_status_t (*lumio_core_last_error_detail)(lumio_handle_t context, lumio_buffer_t out_detail)","csharpSignature":"LumioStatus lumio_core_last_error_detail(LumioHandle context, LumioBuffer out_detail)","name":"lumio_core_last_error_detail","offset":32,"params":[{"name":"context","typeRef":"handle:core_context"},{"name":"out_detail","typeRef":"buffer:out"}],"returns":"status","rustSignature":"extern \"C\" fn(context: LumioHandle, out_detail: LumioBuffer) -> LumioStatus","slotIndex":2}],"version":1},{"declaredStructSize":32,"fields":[{"c":"uint32_t","csharp":"uint","name":"version","offset":0,"rust":"u32","size":4},{"c":"uint32_t","csharp":"uint","name":"struct_size","offset":4,"rust":"u32","size":4},{"c":"uint64_t","csharp":"ulong","name":"reserved0","offset":8,"rust":"u64","size":8}],"functionCount":2,"minimumStructSize":32,"name":"lumio_voxel_api","reservedSlots":0,"slots":[{"cSignature":"lumio_status_t (*lumio_voxel_world_create)(lumio_handle_t context, const struct lumio_voxel_world_desc_v1* desc, lumio_handle_t out_world)","csharpSignature":"LumioStatus lumio_voxel_world_create(LumioHandle context, IntPtr desc, LumioHandle out_world)","name":"lumio_voxel_world_create","offset":16,"params":[{"name":"context","typeRef":"handle:core_context"},{"name":"desc","typeRef":"struct:voxel_world_desc:v1"},{"name":"out_world","typeRef":"handle:voxel_world"}],"returns":"status","rustSignature":"extern \"C\" fn(context: LumioHandle, desc: *const LumioVoxelWorldDescV1, out_world: LumioHandle) -> LumioStatus","slotIndex":0},{"cSignature":"lumio_status_t (*lumio_voxel_world_destroy)(lumio_handle_t world)","csharpSignature":"LumioStatus lumio_voxel_world_destroy(LumioHandle world)","name":"lumio_voxel_world_destroy","offset":24,"params":[{"name":"world","typeRef":"handle:voxel_world"}],"returns":"status","rustSignature":"extern \"C\" fn(world: LumioHandle) -> LumioStatus","slotIndex":1}],"version":1}],"typeMapping":[{"align":1,"c":"uint8_t","csharp":"byte","rust":"u8","size":1,"typeRef":"u8"},{"align":2,"c":"uint16_t","csharp":"ushort","rust":"u16","size":2,"typeRef":"u16"},{"align":4,"c":"uint32_t","csharp":"uint","rust":"u32","size":4,"typeRef":"u32"},{"align":8,"c":"uint64_t","csharp":"ulong","rust":"u64","size":8,"typeRef":"u64"},{"align":1,"c":"int8_t","csharp":"sbyte","rust":"i8","size":1,"typeRef":"i8"},{"align":2,"c":"int16_t","csharp":"short","rust":"i16","size":2,"typeRef":"i16"},{"align":4,"c":"int32_t","csharp":"int","rust":"i32","size":4,"typeRef":"i32"},{"align":8,"c":"int64_t","csharp":"long","rust":"i64","size":8,"typeRef":"i64"},{"align":4,"c":"float","csharp":"float","rust":"f32","size":4,"typeRef":"f32"},{"align":8,"c":"double","csharp":"double","rust":"f64","size":8,"typeRef":"f64"},{"align":4,"c":"uint32_t","csharp":"uint","rust":"u32","size":4,"typeRef":"bool32"},{"align":4,"c":"lumio_status_t","csharp":"LumioStatus","rust":"LumioStatus","size":4,"typeRef":"status"},{"align":8,"c":"lumio_handle_t","csharp":"LumioHandle","rust":"LumioHandle","size":16,"typeRef":"handle:"},{"align":8,"c":"lumio_buffer_t","csharp":"LumioBuffer","rust":"LumioBuffer","size":24,"typeRef":"buffer:in"},{"align":8,"c":"lumio_buffer_t","csharp":"LumioBuffer","rust":"LumioBuffer","size":24,"typeRef":"buffer:out"},{"align":8,"c":"lumio_buffer_t","csharp":"LumioBuffer","rust":"LumioBuffer","size":24,"typeRef":"buffer:inout"},{"align":8,"c":"const lumio__v*","csharp":"IntPtr","rust":"*const LumioV","size":8,"typeRef":"struct::v"},{"align":8,"c":"const lumio_*","csharp":"IntPtr","rust":"*const Lumio","size":8,"typeRef":"ptr:const:"},{"align":8,"c":"lumio_*","csharp":"IntPtr","rust":"*mut Lumio","size":8,"typeRef":"ptr:mut:"}]} diff --git a/xtask/src/contracts.rs b/xtask/src/contracts.rs index ae5bb59..6ce00fe 100644 --- a/xtask/src/contracts.rs +++ b/xtask/src/contracts.rs @@ -210,8 +210,11 @@ fn parse_num(b: &[u8], pos: &mut usize) -> Result { pub const GENERATED_DATA_REL: &str = "crates/lumio-contract-types/src/generated_data.rs"; const BUNDLE_MIRROR_REL: &str = "docs/architecture/abi/root-abi-bundle.json"; const IDS_MIRROR_REL: &str = "docs/architecture/abi/ids-index.json"; +const HEADER_MIRROR_REL: &str = "docs/architecture/abi/lumio_core.h"; /// ADR-046:`ErrorCode` numeric 必须放得进 `lumio_status_t`(int32)。 const STATUS_NUMERIC_MAX: i64 = 2_147_483_647; +/// Header 里 capability 常量的前缀(ADR-040 §7.1 的键空间投影)。 +const CAPABILITY_DEFINE_PREFIX: &str = "#define LUMIO_CAPABILITY_"; fn read_mirror(root: &Path, rel: &str) -> Result { let path = root.join(rel.replace('/', std::path::MAIN_SEPARATOR_STR)); @@ -220,6 +223,66 @@ fn read_mirror(root: &Path, rel: &str) -> Result { parse(&text).map_err(|e| format!("{rel}: {e}")) } +fn read_mirror_text(root: &Path, rel: &str) -> Result { + let path = root.join(rel.replace('/', std::path::MAIN_SEPARATOR_STR)); + std::fs::read_to_string(&path).map_err(|e| format!("read {}: {e}", path.display())) +} + +/// 注册 id 的 SCREAMING_SNAKE 投影,与发布 Header 的宏拼写一致 +/// (`HybridCLR` -> `HYBRID_CLR`)。 +pub fn screaming_snake(id: &str) -> String { + let chars: Vec = id.chars().collect(); + let mut out = String::new(); + for (i, c) in chars.iter().enumerate() { + if i > 0 + && c.is_ascii_uppercase() + && (!chars[i - 1].is_ascii_uppercase() + || chars.get(i + 1).is_some_and(char::is_ascii_lowercase)) + { + out.push('_'); + } + out.push(c.to_ascii_uppercase()); + } + out +} + +/// 镜像 Header 里的 capability 键投影。 +pub struct CapabilityDefines { + /// `(宏名去前缀, 数值)`,例如 `("VOXEL_SPATIAL", 6)`。 + pub keys: Vec<(String, i64)>, + /// `LUMIO_CAPABILITY_COUNT`,缺失为 `None`。 + pub count: Option, +} + +/// 解析镜像 Header 的 capability 常量。 +/// +/// `LUMIO_CAPABILITY_BITS` 共用同一前缀但**不是键**——它是 `capability_bits` +/// 标量,掩码还是计数、以及任何 bit 位指派 V1 均未冻结(D-015 只裁了键空间), +/// 因此在这里被显式排除,不参与任何键推导。 +pub fn parse_capability_defines(header: &str) -> Result { + let mut keys = Vec::new(); + let mut count = None; + for line in header.lines() { + let Some(rest) = line.strip_prefix(CAPABILITY_DEFINE_PREFIX) else { + continue; + }; + let mut parts = rest.split_whitespace(); + let (Some(name), Some(value)) = (parts.next(), parts.next()) else { + return Err(format!("malformed capability define `{line}`")); + }; + let numeric: i64 = value + .trim_end_matches('u') + .parse() + .map_err(|e| format!("bad capability define `{line}`: {e}"))?; + match name { + "BITS" => {} + "COUNT" => count = Some(numeric), + _ => keys.push((name.to_string(), numeric)), + } + } + Ok(CapabilityDefines { keys, count }) +} + /// 从镜像推导 `generated_data.rs` 的完整内容(确定性输出)。 pub fn derive_generated_data(root: &Path) -> Result { let bundle = read_mirror(root, BUNDLE_MIRROR_REL)?; @@ -231,7 +294,7 @@ pub fn derive_generated_data(root: &Path) -> Result { //! Source of truth: the byte-pinned mirrors under `docs/architecture/abi/`\n\ //! (upstream revision in that directory's README). Regenerate with\n\ //! `cargo xtask gen-contracts` after a mirror update; commit together.\n\n\ - use crate::generated::ArchitectureErrorCode;\n\ + use crate::generated::{ArchitectureCapabilityKey, ArchitectureErrorCode};\n\ use crate::layout::{AbiStructGolden, AbiTypeGolden};\n\n", ); @@ -381,8 +444,88 @@ pub fn derive_generated_data(root: &Path) -> Result { seen_numerics.push(numeric); writeln!(out, " ArchitectureErrorCode::new(\"{id}\", {numeric}),").unwrap(); } + out.push_str("];\n\n"); + + // ids/index.json 的 Capability 命名空间(Architecture 所有)。D-015 裁决 + // (ADR-040 §7.1):注册表是键空间唯一权威,架构生成器是唯一发射方,下游 + // 消费投影;仓内私有键值表即违规。numeric 是枚举序号,不是 bit 位。 + let capability_ns = ids + .get("namespaces")? + .as_arr()? + .iter() + .find(|ns| ns.get("namespace").and_then(Json::as_str).ok() == Some("Capability")) + .ok_or("ids-index.json missing Capability namespace")?; + if capability_ns.get("owner")?.as_str()? != "Architecture" { + return Err("Capability namespace owner is not Architecture".to_string()); + } + + // Header 是同一权威的另一形态;两侧不一致说明镜像半新半旧,直接失败。 + let header = read_mirror_text(root, HEADER_MIRROR_REL)?; + let header_defines = parse_capability_defines(&header)?; + let header_keys = &header_defines.keys; + + let mut seen_ids: Vec = Vec::new(); + let mut seen_numerics: Vec = Vec::new(); + out.push_str( + "#[rustfmt::skip]\npub(crate) const CAPABILITY_KEYS: &[ArchitectureCapabilityKey] = &[\n", + ); + for value in capability_ns.get("values")?.as_arr()? { + let id = value.get("id")?.as_str()?; + let numeric = value.get("numeric")?.as_i64()?; + let status = value.get("status")?.as_str()?; + if status != "Active" && status != "Reserved" { + return Err(format!("Capability {id} has unexpected status {status}")); + } + if numeric <= 0 { + return Err(format!( + "Capability {id} numeric {numeric} is not a 1-based ordinal" + )); + } + if seen_ids.iter().any(|s| s == id) || seen_numerics.contains(&numeric) { + return Err(format!("Capability duplicate id/numeric: {id}/{numeric}")); + } + let macro_name = screaming_snake(id); + match header_keys.iter().find(|(name, _)| *name == macro_name) { + Some((_, header_numeric)) if *header_numeric == numeric => {} + Some((_, header_numeric)) => { + return Err(format!( + "header LUMIO_CAPABILITY_{macro_name} = {header_numeric} disagrees with registry {id} = {numeric}" + )); + } + None => { + return Err(format!( + "header is missing LUMIO_CAPABILITY_{macro_name} for registered Capability {id}" + )); + } + } + seen_ids.push(id.to_string()); + seen_numerics.push(numeric); + writeln!( + out, + " ArchitectureCapabilityKey::new(\"{id}\", {numeric}, \"{status}\")," + ) + .unwrap(); + } out.push_str("];\n"); + for (name, _) in header_keys { + if !seen_ids.iter().any(|id| screaming_snake(id) == *name) { + return Err(format!( + "header publishes LUMIO_CAPABILITY_{name} with no registered Capability value" + )); + } + } + match header_defines.count { + Some(count) if count == seen_ids.len() as i64 => {} + Some(count) => { + return Err(format!( + "header LUMIO_CAPABILITY_COUNT = {count} != {} registered Capability values", + seen_ids.len() + )); + } + None => return Err("header is missing LUMIO_CAPABILITY_COUNT".to_string()), + } + Ok(out) }